From f41dde422290b08e3c540732e778745dab08bb6a Mon Sep 17 00:00:00 2001 From: frequent Date: Tue, 9 Jul 2013 23:04:57 +0200 Subject: [PATCH 01/13] filter widget first commit --- css/structure/jquery.mobile.filterbar.css | 25 + js/widgets/filter.js | 314 ++++++++ tests/integration/filter/filter_core.js | 942 ++++++++++++++++++++++ tests/integration/filter/index.html | 450 +++++++++++ 4 files changed, 1731 insertions(+) create mode 100644 css/structure/jquery.mobile.filterbar.css create mode 100644 js/widgets/filter.js create mode 100644 tests/integration/filter/filter_core.js create mode 100644 tests/integration/filter/index.html diff --git a/css/structure/jquery.mobile.filterbar.css b/css/structure/jquery.mobile.filterbar.css new file mode 100644 index 00000000000..1000b8a55d9 --- /dev/null +++ b/css/structure/jquery.mobile.filterbar.css @@ -0,0 +1,25 @@ +.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; +} + + + diff --git a/js/widgets/filter.js b/js/widgets/filter.js new file mode 100644 index 00000000000..1d5d295cdc5 --- /dev/null +++ b/js/widgets/filter.js @@ -0,0 +1,314 @@ +//>>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 ) { + "use strict"; + + // 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", $.mobile.widget, $.extend( { + + options: { + filterTheme: "a", + filterPlaceholder: "Filter items...", + filterReveal: false, + filterCallback: defaultfilterCallback, + classes: "", + id: null, + inset: false, + enhance: true, + target: null, + mini: false, + selector: null + }, + + _onKeyUp: function() { + var self = this, + search = self._search[ 0 ], + o = self.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 = window.setTimeout(function() { + + self._trigger( "beforefilter", "beforefilter", { input: search } ); + + // Change val as lastval for next execution + search.setAttribute( "data-" + $.mobile.ns + "lastval" , val ); + + self._filterItems( search, val, lastval ) + }, 250); + }, + + _getFilterableItems: function() { + var self = this, + el = self.element, + o = self.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 self = this, + o = self.options, + filterItems = [], + isCustomfilterCallback = o.filterCallback !== defaultfilterCallback, + _getFilterableItems = self._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 self = this, + el = self.element, + o = self.options, + getAttrFixed = $.mobile.getAttribute, + filterItems = self._setFilterableItems(val, lastval), + _getFilterableItems = self._getFilterableItems(), + childItems = false, + itemtext = "", + item, + select = self.element.parents( ".ui-select" ), + i; + + self._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; + } + } + + self._toggleFilterableItems( filterItems, select, o.filterReveal , true); + } else { + self._toggleFilterableItems( filterItems, select, o.filterReveal ); + } + + self._addFirstLastClasses( _getFilterableItems, self._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 () { + var self = this, + el = this.element, + o = self.options, + wrapper = $( "
", { + "class": o.classes + " ui-filter ", + "role": "search", + "id" : o.id || "ui-filter-" + self.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 ( typeof o.target === "string" ) { + wrapper.prependTo( $( "." + o.target + "" ) ); + } else { + wrapper.insertBefore( el ); + } + + return search; + }, + + _create: function() { + var self = this, + o = self.options, + search, + items = self._getFilterableItems(); + + if ( o.filterReveal ) { + items.addClass( "ui-screen-hidden" ); + } + + self._setOption( "timer", undefined ); + + if (o.enhance) { + search = self._enhance(); + } else { + // NOTE: DIY requires data-id, otherwise how do we find the search + // input. We could always wrap the filterable element (e.g. ul) in + // ui-filter as well, but I'm not sure I want to move elements around + // that much. + search = $( "#" + o.id ).find( "input" ); + } + + self._on( search, { keyup: "_onKeyUp", change: "_onKeyUp", input: "_onKeyUp" } ); + + $.extend( self, { + _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. + self._addFirstLastClasses( items, self._getVisibles( items, true ), true ); + }, + + _setOptions: function( options ) { + var self = this, + key; + + for ( key in options ) { + self._setOption( key, options[ key ] ); + } + + return self; + }, + + _setOption: function( key, value ) { + var self = this, + o = self.options, + wrapper = document.getElementById( o.id || "ui-filter-" + self.uuid ), + $input = $( wrapper ).find( "input" ); + + // always update + o[ key ] = value; + + if ( key === "disabled" ) { + $input + .toggleClass( self.widgetFullName + "-disabled ui-state-disabled", !!value ) + .attr( "aria-disabled", value ) + .textinput( value ? "disable" : "enable" ); + } + return self; + }, + + widget: function() { + return this.element + }, + + enable: function() { + return this._setOption( "disabled", false ); + }, + + disable: function() { + return this._setOption( "disabled", true ); + }, + + destroy: function() { + var self = this, + o = self.options, + wrapper = document.getElementById( o.id || "ui-filter-" + self.uuid ); + + if ( o.enhance ) { + wrapper.parentNode.removeChild( wrapper ); + } + self._toggleFilterableItems( self._getFilterableItems(), false, false ); + self._destroy(); + } + + }, $.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/tests/integration/filter/filter_core.js b/tests/integration/filter/filter_core.js new file mode 100644 index 00000000000..6af26edeeef --- /dev/null +++ b/tests/integration/filter/filter_core.js @@ -0,0 +1,942 @@ +/* + * mobile filter unit tests - listview + */ + +// TODO split out into seperate test files +(function($){ + var home = $.mobile.path.parseUrl( location.href ).pathname + location.search, + insetVal = $.mobile.filterbar.prototype.options.inset; + + $.mobile.defaultTransition = "none"; + + module( "Filter Widget Core Functions" ); + + var searchFilterId = "#search-filter-test"; + + asyncTest( "Filter downs results when the user enters information", function() { + var $searchPage = $(searchFilterId); + $.testHelper.pageSequence([ + function() { + $.mobile.changePage(searchFilterId); + }, + function() { + $searchPage.find('input').val('at'); + $searchPage.find('input').trigger('change'); + setTimeout(function() { + deepEqual($searchPage.find('li.ui-screen-hidden').length, 2); + start(); + }, 500); + } + ]); + }); + + asyncTest( "Redisplay results when user removes values", function() { + var $searchPage = $(searchFilterId); + $.testHelper.pageSequence([ + function() { + $.mobile.changePage(searchFilterId); + }, + function() { + $searchPage.find('input').val('a'); + $searchPage.find('input').trigger('change'); + deepEqual($searchPage.find("li[style^='display: none;']").length, 0); + start(); + } + ]); + }); + + asyncTest( "Filter downs results with multiple entries by user", function() { + var $searchPage = $("#search-filter-test-multiple"); + $.testHelper.pageSequence([ + function() { + $.mobile.changePage("#search-filter-test-multiple"); + }, + function() { + // first input + + $searchPage.find('input').val('a'); + $searchPage.find('input').trigger('change'); + window.setTimeout(function() { + deepEqual( + $searchPage.find('li.ui-screen-hidden').length, + 3, + "Filtering hides non matching columns" + ); + // second input + $searchPage.find('input').val('aa'); + $searchPage.find('input').trigger('change'); + window.setTimeout(function() { + deepEqual( + $searchPage.find('li.ui-screen-hidden').length, + 4, + "Filtering again hides all columns" + ); + // clear last input + $searchPage.find('input').val('a'); + $searchPage.find('input').trigger('change'); + window.setTimeout(function() { + deepEqual( + $searchPage.find('li.ui-screen-hidden').length, + 3, + "Removing one character shows some columns" + ); + // empty input + $searchPage.find('input').val(''); + $searchPage.find('input').trigger('change'); + window.setTimeout(function() { + deepEqual( + $searchPage.find('li.ui-screen-hidden').length, + 0, + "Emptying input shows all columns" + ); + start(); + },500); + },500); + },500); + }, 500); + } + ]); + }); + + asyncTest( "Filter works fine with \\W- or regexp-special-characters", + function() { + var $searchPage = $(searchFilterId); + $.testHelper.pageSequence([ + function() { + $.mobile.changePage(searchFilterId); + }, + function() { + $searchPage.find('input').val('*'); + $searchPage.find('input').trigger('change'); + setTimeout(function() { + deepEqual($searchPage.find('li.ui-screen-hidden').length, 4); + start(); + }, 500); + } + ] + ); + }); + + asyncTest( "Event filterbarbeforefilter firing", function() { + var $searchPage = $( searchFilterId ); + $.testHelper.pageSequence([ + function() { + $.mobile.changePage( searchFilterId ); + }, + + function() { + var beforeFilterCount = 0; + $searchPage.on( "filterbarbeforefilter", function( e ) { + beforeFilterCount += 1; + }); + + $searchPage.find( 'input' ).val( "a" ); + $searchPage.find( 'input' ).trigger('input'); + $searchPage.find( 'input' ).trigger('keyup'); + $searchPage.find( 'input' ).trigger('change'); + window.setTimeout(function() { + equal( + beforeFilterCount, + 1, + "filterbarbeforefilter should fire only once for the same value" + ); + $searchPage.find( 'input' ).val( "ab" ); + $searchPage.find( 'input' ).trigger('input'); + $searchPage.find( 'input' ).trigger('keyup'); + window.setTimeout(function() { + equal( + beforeFilterCount, + 2, + "filterbarbeforefilter should fire twice since value has changed" + ); + start(); + }, 500); + }, 500); + } + ]); + }); + + asyncTest( "Filter downs results and dividers when the user enters information", + function() { + var $searchPage = $("#search-filter-with-dividers-test"); + $.testHelper.pageSequence([ + function() { + $.mobile.changePage("#search-filter-with-dividers-test"); + }, + // wait for the page to become active/enhanced + function(){ + $searchPage.find('input').val('at'); + $searchPage.find('input').trigger('change'); + setTimeout(function() { + //there should be four hidden list entries + deepEqual( + $searchPage.find('li.ui-screen-hidden').length, + 4 + ); + //there should be two list entries that are list dividers and hidden + deepEqual( + $searchPage + .find('li.ui-screen-hidden:jqmData(role=list-divider)') + .length, + 2 + ); + //there should be two list entries that are not list dividers and hidden + deepEqual( + $searchPage + .find('li.ui-screen-hidden:not(:jqmData(role=list-divider))') + .length, + 2 + ); + start(); + }, 500); + } + ]); + } + ); + + asyncTest( "Redisplay results when user removes values", function() { + $.testHelper.pageSequence([ + function() { + $.mobile.changePage("#search-filter-with-dividers-test"); + }, + + function() { + $('.ui-page-active input').val('a'); + $('.ui-page-active input').trigger('change'); + + setTimeout(function() { + deepEqual($('.ui-page-active input').val(), 'a'); + deepEqual($('.ui-page-active li[style^="display: none;"]').length, 0); + start(); + }, 500); + } + ]); + }); + + asyncTest( "Dividers are hidden when preceding hidden rows and shown when preceding shown rows", function () { + $.testHelper.pageSequence([ + function() { + $.mobile.changePage("#search-filter-with-dividers-test"); + }, + + function() { + var $page = $('.ui-page-active'); + + $page.find('input').val('at'); + $page.find('input').trigger('change'); + + setTimeout(function() { + deepEqual($page.find('li:jqmData(role=list-divider):hidden').length, 2); + deepEqual($page.find('li:jqmData(role=list-divider):hidden + li:not(:jqmData(role=list-divider)):hidden').length, 2); + deepEqual($page.find('li:jqmData(role=list-divider):not(:hidden) + li:not(:jqmData(role=list-divider)):not(:hidden)').length, 2); + start(); + }, 500); + } + ]); + }); + + asyncTest( "Inset List View should refresh corner classes after filtering", 4 * 2, function () { + var checkClasses = function() { + var $page = $( ".ui-page-active" ), + $li = $page.find( "li:visible" ); + ok($li.first().hasClass( "ui-first-child" ), $li.length+" li elements: First visible element should have class ui-first-child"); + ok($li.last().hasClass( "ui-last-child" ), $li.length+" li elements: Last visible element should have class ui-last-child"); + }; + + $.testHelper.pageSequence([ + function() { + $.mobile.changePage("#search-filter-inset-test"); + }, + + function() { + var $page = $('.ui-page-active'); + $.testHelper.sequence([ + function() { + checkClasses(); + + $page.find('input').val('man'); + $page.find('input').trigger('change'); + }, + + function() { + checkClasses(); + + $page.find('input').val('at'); + $page.find('input').trigger('change'); + }, + + function() { + checkClasses(); + + $page.find('input').val('catwoman'); + $page.find('input').trigger('change'); + }, + + function() { + checkClasses(); + start(); + } + ], 50); + } + ]); + }); + + module( "Filter Widget Custom Filter", { + setup: function() { + var self = this; + this._refreshCornersCount = 0; + this._refreshCornersFn = $.mobile.filterbar.prototype._addFirstLastClasses; + + // _refreshCorners is the last method called in the filter loop + // so we count the number of times _refreshCorners gets invoked to stop the test + $.mobile.filterbar.prototype._addFirstLastClasses = function() { + self._refreshCornersCount += 1; + self._refreshCornersFn.apply( this, arguments ); + } + }, + teardown: function() { + $.mobile.filterbar.prototype._refreshCorners = this._refreshCornersFn; + } + }); + + asyncTest( "Custom filterCallback should cause iteration on all list elements", function(){ + var listPage = $( "#search-customfilter-test" ), + filterCallbackCount = 0, + expectedCount = 2 * listPage.find("li").length; + expect( 1 ); + + $.testHelper.pageSequence( [ + function(){ + //reset for relative url refs + $.mobile.changePage( home ); + }, + + function() { + $.mobile.changePage( "#search-customfilter-test" ); + }, + + function() { + // set the listview instance callback + listPage.find( "ul" ).filterbar( "option", "filterCallback", function( text, searchValue, item ) { + filterCallbackCount += 1; + return text.toString().toLowerCase().indexOf( searchValue ) === -1; + }); + + // trigger a change in the search filter + listPage.find( "input" ).val( "at" ).trigger( "change" ); + // need to wait because of the filterdelay + window.setTimeout(function() { + listPage.find( "input" ).val( "atw" ).trigger( "change" ); + },500); + }, + + function() { + equal( filterCallbackCount, expectedCount, "filterCallback should be called exactly "+ expectedCount +" times" ); + start(); + } + ]); + }); + + asyncTest( "filterCallback can be altered after widget creation", function(){ + var listPage = $( "#search-customfilter-test" ), + filterChangedCallbackCount = 0, + expectedCount = 1 * listPage.find("li").length, + runtest; + expect( 1 ); + + $.testHelper.pageSequence( [ + function(){ + //reset for relative url refs + $.mobile.changePage( home ); + }, + + function() { + $.mobile.changePage( "#search-customfilter-test" ); + }, + + function() { + // set the filter instance callback + listPage.find( "ul" ).filterbar( "option", "filterCallback", function() { + filterChangedCallbackCount += 1; + }); + + listPage.find( "input" ).val( "foo" ) + listPage.find( "input" ).trigger( "change" ); + }, + + function() { + equal( filterChangedCallbackCount, expectedCount, "filterChangeCallback should be called exactly "+ expectedCount +" times" ); + start(); + } + ]); + }); + + module( "Filter Widget Reveal/Autocomplete" ); + + asyncTest( "Filter downs results when the user enters information", 3, function() { + var $searchPage = $( "#search-filter-reveal-test" ); + + $.testHelper.pageSequence([ + function() { + $.mobile.changePage( $searchPage ); + }, + + function() { + deepEqual( $searchPage.find( 'li.ui-screen-hidden' ).length, 22); + }, + + function() { + $searchPage.find( 'input' ).val( 'a' ); + $searchPage.find( 'input' ).trigger('change'); + window.setTimeout(function() { + deepEqual( $searchPage.find('li.ui-screen-hidden').length, 11); + },500); + }, + + function() { + $searchPage.find( 'input' ).val( '' ); + $searchPage.find( 'input' ).trigger('change'); + window.setTimeout(function() { + deepEqual( $searchPage.find('li.ui-screen-hidden').length, 22); + start(); + },500); + } + ]); + }); + + module( "Filter Widget Configuration" ); + + asyncTest( "Custom id and classes are set on filter", function () { + $.testHelper.pageSequence( [ + function(){ + $.mobile.changePage( home ); + }, + + function() { + $.mobile.changePage( "#search-custom-id-classes-test" ); + }, + + function() { + var $page = $( ".ui-page-active" ), + $filter = $page.find( ".ui-filter" ), + $list = $page.find( "ul" ); + + ok($filter.hasClass( "baz" ), "filter element has custom classed set by user"); + ok($filter.attr( "id" ) === "foo", "filter has custom id"); + + $list.filterbar("destroy"); + + ok($page.find( ".ui-filter" ).length === 0, "filter can be destroyed using custom user id"); + start(); + } + ]); + }); + + asyncTest( "Placing the filter at a location specified by data-target", function () { + $.testHelper.pageSequence( [ + function(){ + $.mobile.changePage( home ); + }, + + function() { + $.mobile.changePage( "#search-target-test" ); + }, + + function() { + var $page = $( ".ui-page-active" ), + $filter = $page.find( ".ui-filter" ), + $list = $page.find( "ul" ); + + ok($filter.parent().hasClass( "baz" ), "filter appended to element specified by data-target") + + $page.find('input').val('ac'); + $page.find('input').trigger('change'); + setTimeout(function() { + deepEqual($list.find('li.ui-screen-hidden').length, 3); + start(); + }, 500); + } + ]); + }); + + asyncTest( "Selector attribute allows filtering of multiple datasets", function () { + $.testHelper.pageSequence( [ + function(){ + $.mobile.changePage( home ); + }, + + function() { + $.mobile.changePage( "#search-selector-test" ); + }, + + function() { + var $page = $( ".ui-page-active" ), + $filter = $page.find( ".ui-filter" ), + $list_a = $page.find( "ul" ).eq(0), + $list_b = $page.find( "ul" ).eq(1); + + $page.find('input').val('ac'); + $page.find('input').trigger('change'); + setTimeout(function() { + deepEqual($list_a.find('li.ui-screen-hidden').length, $list_b.find('li.ui-screen-hidden').length); + start(); + }, 500); + } + ]); + }); + + asyncTest( "Filter can be set pre-enhanced (if data-id is provided)", function () { + $.testHelper.pageSequence( [ + function(){ + $.mobile.changePage( home ); + }, + + function() { + $.mobile.changePage( "#search-pre-enhance-test" ); + }, + + function() { + var $page = $( ".ui-page-active" ), + $filter = $page.find( ".ui-filter" ), + $list = $page.find( "ul" ).eq(0); + + $page.find('input').val('ac'); + $page.find('input').trigger('change'); + setTimeout(function() { + deepEqual( + $list.find('li.ui-screen-hidden').length, + 3, + "Custom filter can be used for filtering" + ); + $list.filterbar( "destroy" ); + ok( + $page.find( ".ui-filter" ).length === 1, + "Pre-enhanced filter element is not removed on destroy" + ); + deepEqual( + $list.find('li.ui-screen-hidden').length, + 0, + "destroying a filter shows all elements" + ); + start(); + }, 500); + } + ]); + }); + + module( "Filter Widget Methods/Options" ); + + asyncTest( "Disabling, enabling text input", function () { + $.testHelper.pageSequence( [ + function(){ + $.mobile.changePage( home ); + }, + + function() { + $.mobile.changePage( "#search-disable-test" ); + }, + + function() { + var $page = $( ".ui-page-active" ), + $filter = $page.find( ".ui-filter" ), + $list = $page.find( "ul" ).eq(0); + + $list.filterbar( "disable" ); + + deepEqual( + $page.find('input').attr( "disabled" ), + "disabled", + "Setting disable option on widget (ul) disables filter textinput" + ); + + $page.find('input').val('ac'); + $page.find('input').trigger('change'); + setTimeout(function() { + deepEqual( + $list.find('li.ui-screen-hidden').length, + 0, + "Disabled filters cannot filter" + ); + + $list.filterbar( "enable" ); + + deepEqual( + $page.find('input').attr( "disabled" ), + undefined, + "Enabling widget also enables textinput" + ); + + $page.find('input').val('ac'); + $page.find('input').trigger('change'); + setTimeout(function() { + deepEqual( + $list.find('li.ui-screen-hidden').length, + 3, + "Enabled filter is working again" + ); + start(); + },500); + }, 500); + } + ]); + }); + + module( "Filter Widget Using Different Elements" ); + + asyncTest( "Filtering Table Rows based on Cells", function () { + $.testHelper.pageSequence( [ + function(){ + $.mobile.changePage( home ); + }, + + function() { + $.mobile.changePage( "#search-table-test" ); + }, + + function() { + var $page = $( ".ui-page-active" ), + $filter = $page.find( ".ui-filter" ), + $table = $page.find( "table" ).eq(0); + + $page.find('input').val('12:12'); + $page.find('input').trigger('change'); + setTimeout(function() { + deepEqual( + $table.find('.ui-screen-hidden').length, + 4, + "Filtering table rows hides based on table cell values" + ); + $page.find('input').val(''); + $page.find('input').trigger('change'); + setTimeout(function() { + deepEqual( + $table.find('.ui-screen-hidden').length, + 0, + "Removing filter value shows all table rows again" + ); + start(); + }, 500); + }, 500); + } + ]); + }); + + asyncTest( "Controlgroup Search Filter", function () { + $.testHelper.pageSequence( [ + function(){ + $.mobile.changePage( home ); + }, + + function() { + $.mobile.changePage( "#search-controlgroup-test" ); + }, + + function() { + var $page = $( ".ui-page-active" ), + $filter = $page.find( ".ui-filter" ), + $controlgroup = $page.find( "div.helper" ); + + // filter + $page.find('input').val('ac'); + $page.find('input').trigger('change'); + setTimeout(function() { + deepEqual( + $controlgroup.find('.ui-screen-hidden').length, + 3, + "Filtering controlgroup input/a buttons by value" + ); + + // clear + $page.find('input').val(''); + $page.find('input').trigger('change'); + setTimeout(function() { + deepEqual( + $controlgroup.find('.ui-screen-hidden').length, + 0, + "Removing filter value shows all controlgroup buttons again" + ); + start(); + }, 500); + }, 500); + } + ]); + }); + + asyncTest( "Native Select Search Filter", function () { + $.testHelper.pageSequence( [ + function(){ + $.mobile.changePage( home ); + }, + + function() { + $.mobile.changePage( "#search-select-test" ); + }, + + function() { + var $page = $( ".ui-page-active" ), + $filter = $page.find( ".ui-filter" ), + $select = $page.find( ".ui-select" ); + + // filter + $page.find('input').val('a'); + $page.find('input').trigger('change'); + setTimeout(function() { + deepEqual( + $select.find('.ui-screen-hidden').length, + 9, + "Filtering select options by option text" + ); + + // clear + $page.find('input').val(''); + $page.find('input').trigger('change'); + setTimeout(function() { + deepEqual( + $select.find('.ui-screen-hidden').length, + 0, + "Removing filter value shows all select options again" + ); + start(); + }, 500); + }, 500); + } + ]); + }); + + asyncTest( "Native Select Search Filter - using data-filtertext", function () { + $.testHelper.pageSequence( [ + function(){ + $.mobile.changePage( home ); + }, + + function() { + $.mobile.changePage( "#search-select-test" ); + }, + + function() { + var $page = $( ".ui-page-active" ), + $filter = $page.find( ".ui-filter" ), + $select = $page.find( ".ui-select" ); + + // filter + $page.find('input').val('this goes'); + $page.find('input').trigger('change'); + setTimeout(function() { + deepEqual( + $select.find('.ui-screen-hidden').length, + 9, + "Filtering select options by option text" + ); + + // clear + $page.find('input').val(''); + $page.find('input').trigger('change'); + setTimeout(function() { + deepEqual( + $select.find('.ui-screen-hidden').length, + 0, + "Removing filter value shows all select options again" + ); + start(); + }, 500); + }, 500); + } + ]); + }); + + asyncTest( "Native Select Search Filter - select is hidden if no options match", function () { + $.testHelper.pageSequence( [ + function(){ + $.mobile.changePage( home ); + }, + + function() { + $.mobile.changePage( "#search-select-test" ); + }, + + function() { + var $page = $( ".ui-page-active" ), + $filter = $page.find( ".ui-filter" ), + $select = $page.find( ".ui-select" ); + + // filter + $page.find('input').val('aaaaaaaa'); + $page.find('input').trigger('change'); + setTimeout(function() { + ok( + $select.is( '.ui-screen-hidden' ), + "Select element itself is hidden when no options match filter" + ); + + // clear + $page.find('input').val(''); + $page.find('input').trigger('change'); + setTimeout(function() { + deepEqual( + $select.is( '.ui-screen-hidden' ), + false, + "Clearing filter also shows select again" + ); + start(); + }, 500); + }, 500); + } + ]); + }); + /* ================= the next block is tested on different elements ======= */ + asyncTest( "Random Elements Filter -

", function () { + $.testHelper.pageSequence( [ + function(){ + $.mobile.changePage( home ); + }, + + function() { + $.mobile.changePage( "#search-random-test-p" ); + }, + + function() { + var $page = $( ".ui-page-active" ), + $filter = $page.find( ".ui-filter" ).eq(1), + $selection = $page.find( ".elements_p" ); + $selection2 = $page.find( ".elements_b_p" ); + + // filter + $page.find('input').eq(0).val('b'); + $page.find('input').eq(0).trigger('change'); + setTimeout(function() { + deepEqual( + $selection.find('.ui-screen-hidden').length, + 5, + "Filtering

elements by text" + ); + + // clear + $page.find('input').eq(0).val(''); + $page.find('input').eq(0).trigger('change'); + setTimeout(function() { + deepEqual( + $selection.find('.ui-screen-hidden').length, + 0, + "Clearing filter shows all elements again" + ); + + // filter second set + $page.find('input').eq(1).val('f'); + $page.find('input').eq(1).trigger('change'); + setTimeout(function() { + deepEqual( + $selection2.find('.ui-screen-hidden').length, + 5, + "Filtering

on 2nd set using data-filtertext works" + ); + deepEqual( + $selection.find('.ui-screen-hidden').length, + 0, + "Filtering

on 2nd set does not change first dataset" + ); + ok(( + $selection2.children().not( '.ui-screen-hidden' ).length === 1 && + $selection2.children().not( '.ui-screen-hidden' ).text() === "a" + ), "Filtering works on data-filtertext and not text" + ); + // clear + $page.find('input').eq(1).val(''); + $page.find('input').eq(1).trigger('change'); + setTimeout(function() { + deepEqual( + $selection2.find('.ui-screen-hidden').length, + 0, + "Clearing filter shows all elements again" + ); + $page.find( "#ticktick_p" ).filterbar("destroy"); + ok( + $page.find(".ui-filter").length === 1, + "Destroying one filter does not destroy another filter" + ); + start(); + },500); + }, 500); + }, 500); + }, 500); + } + ]); + }); + + asyncTest( "Random Elements Filter - ", function () { + $.testHelper.pageSequence( [ + function(){ + $.mobile.changePage( home ); + }, + + function() { + $.mobile.changePage( "#search-random-test-span" ); + }, + + function() { + var $page = $( ".ui-page-active" ), + $filter = $page.find( ".ui-filter" ).eq(1), + $selection = $page.find( ".elements_span" ); + $selection2 = $page.find( ".elements_b_span" ); + + // filter + $page.find('input').eq(0).val('b'); + $page.find('input').eq(0).trigger('change'); + setTimeout(function() { + deepEqual( + $selection.find('.ui-screen-hidden').length, + 5, + "Filtering elements by text" + ); + + // clear + $page.find('input').eq(0).val(''); + $page.find('input').eq(0).trigger('change'); + setTimeout(function() { + deepEqual( + $selection.find('.ui-screen-hidden').length, + 0, + "Clearing filter shows all elements again" + ); + + // filter second set + $page.find('input').eq(1).val('f'); + $page.find('input').eq(1).trigger('change'); + setTimeout(function() { + deepEqual( + $selection2.find('.ui-screen-hidden').length, + 5, + "Filtering on 2nd set using data-filtertext works" + ); + deepEqual( + $selection.find('.ui-screen-hidden').length, + 0, + "Filtering on 2nd set does not change first dataset" + ); + ok(( + $selection2.children().not( '.ui-screen-hidden' ).length === 1 && + $selection2.children().not( '.ui-screen-hidden' ).text() === "a" + ), "Filtering works on data-filtertext and not text" + ); + // clear + $page.find('input').eq(1).val(''); + $page.find('input').eq(1).trigger('change'); + setTimeout(function() { + deepEqual( + $selection2.find('.ui-screen-hidden').length, + 0, + "Clearing filter shows all elements again" + ); + $page.find( "#ticktick_span" ).filterbar("destroy"); + ok( + $page.find(".ui-filter").length === 1, + "Destroying one filter does not destroy another filter" + ); + start(); + },500); + }, 500); + }, 500); + }, 500); + } + ]); + }); +})(jQuery); diff --git a/tests/integration/filter/index.html b/tests/integration/filter/index.html new file mode 100644 index 00000000000..7ef9bd74d5b --- /dev/null +++ b/tests/integration/filter/index.html @@ -0,0 +1,450 @@ + + + + + + jQuery Mobile Filter Integration Test + + + + + + + + + + + + + + + + + +

jQuery Mobile Filter Integration Test

+

+

+
    +
+ + +
+
+

Split List View

+
+
+
    +
  • a is for aquaman
  • +
  • b is for batman
  • +
  • c is for catwoman
  • +
  • d is for darkwing
  • +
+
+
+ + +
+
+

Split List View Multiple Entries

+
+
+
    +
  • a
  • +
  • b
  • +
  • c
  • +
  • d
  • +
+
+
+ + +
+
+

Filtered List View

+
+
+
    +
  • a is for aquaman
  • +
  • b is for batman
  • +
  • c is for catwoman
  • +
  • d is for darkwing
  • +
+
+
+ + +
+
+

Split List View

+
+
+
    +
  • a
  • +
  • a is for aquaman
  • +
  • b
  • +
  • b is for batman
  • +
  • c
  • +
  • c is for catwoman
  • +
  • d
  • +
  • d is for darkwing
  • +
+
+
+ + +
+
+

Inset Filter List View

+
+
+
    +
  • a is for aquaman
  • +
  • b is for batman
  • +
  • c is for catwoman
  • +
  • d is for darkwing
  • +
+
+
+ + +
+
+

Reveal Listview

+
+
+
    +
  • Acura
  • +
  • Audi
  • +
  • BMW
  • +
  • Cadillac
  • +
  • Chrysler
  • +
  • Dodge
  • +
  • Ferrari
  • +
  • Ford
  • +
  • GMC
  • +
  • Honda
  • +
  • Hyundai
  • +
  • Infiniti
  • +
  • Jeep
  • +
  • Kia
  • +
  • Lexus
  • +
  • Mini
  • +
  • Nissan
  • +
  • Porsche
  • +
  • Subaru
  • +
  • Toyota
  • +
  • Volkswagon
  • +
  • Volvo
  • +
+
+
+ + +
+
+

Using custom id and classes

+
+
+
    +
  • Acura
  • +
  • Audi
  • +
  • BMW
  • +
  • Cadillac
  • +
  • Chrysler
  • +
+
+
+ + +
+
+

Using target selector

+
+
+ +
    +
  • Acura
  • +
  • Audi
  • +
  • BMW
  • +
  • Cadillac
  • +
  • Chrysler
  • +
+

a

+

b

+

a

+

b

+

a

+

b

+
+

b

+
+
+ + +
+
+

Using selector to filter multiple sets

+
+
+
+
    +
  • Acura
  • +
  • Audi
  • +
  • BMW
  • +
  • Cadillac
  • +
  • Chrysler
  • +
+
    +
  • Acura
  • +
  • Audi
  • +
  • BMW
  • +
  • Cadillac
  • +
  • Chrysler
  • +
+
+
+ + +
+
+

Using pre-enhanced filter

+
+
+
+ +
+
    +
  • Acura
  • +
  • Audi
  • +
  • BMW
  • +
  • Cadillac
  • +
  • Chrysler
  • +
+
+
+ + +
+
+

Disabling a filter

+
+
+
    +
  • Acura
  • +
  • Audi
  • +
  • BMW
  • +
  • Cadillac
  • +
  • Chrysler
  • +
+
+
+ + +
+
+

Filtering different elements: table

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CompanyLast TradeTrade TimeChangePrev CloseOpenStuff
BidAsk1y Target Est
GOOG Google Inc.597.7412:12PM14.81 (2.54%)582.93597.95597.73 x 100597.91 x 300731.10
AAPL Apple Inc.378.9412:22PM5.74 (1.54%)373.20381.02378.92 x 300378.99 x 100505.94
AMZN Amazon.com Inc.191.5512:23PM3.16 (1.68%)188.39194.99191.52 x 300191.58 x 100240.32
ORCL Oracle Corporation31.1512:44PM1.41 (4.72%)29.7430.6731.14 x 650031.15 x 320036.11
MSFT Microsoft Corporation25.5012:27PM0.66 (2.67%)24.8425.3725.50 x 7110025.51 x 1780031.50
+
+
+ + +
+
+

Filtering different elements: controlgroup

+
+
+
+ + + + + + acporsche +
+
+
+ + +
+
+

Filtering different elements: select

+
+
+
+ + +
+
+ + +
+
+

Filtering different elements: p

+
+
+
+
+

a

+

b

+

c

+

d

+

e

+

f

+
+
+
+

a

+

b

+

c

+

d

+

e

+

f

+
+
+
+ + +
+
+

Filtering different elements: span

+
+
+
+
+ a + b + c + d + e + f +
+
+
+ a + b + c + d + e + f +
+
+
+ + + From b365e9d7379cff821bc4a50301c87531d8515a69 Mon Sep 17 00:00:00 2001 From: frequent Date: Wed, 10 Jul 2013 08:21:30 +0200 Subject: [PATCH 02/13] linting --- js/widgets/filter.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/js/widgets/filter.js b/js/widgets/filter.js index 1d5d295cdc5..b42945dac05 100644 --- a/js/widgets/filter.js +++ b/js/widgets/filter.js @@ -54,7 +54,7 @@ define( [ "jquery", "./forms/textinput" ], function( jQuery ) { // Change val as lastval for next execution search.setAttribute( "data-" + $.mobile.ns + "lastval" , val ); - self._filterItems( search, val, lastval ) + self._filterItems( search, val, lastval ); }, 250); }, @@ -97,7 +97,6 @@ define( [ "jquery", "./forms/textinput" ], function( jQuery ) { _filterItems: function( search, val, lastval ){ var self = this, - el = self.element, o = self.options, getAttrFixed = $.mobile.getAttribute, filterItems = self._setFilterableItems(val, lastval), @@ -253,7 +252,9 @@ define( [ "jquery", "./forms/textinput" ], function( jQuery ) { key; for ( key in options ) { + if(options.hasOwnProperty(key)) { self._setOption( key, options[ key ] ); + } } return self; @@ -278,7 +279,7 @@ define( [ "jquery", "./forms/textinput" ], function( jQuery ) { }, widget: function() { - return this.element + return this.element; }, enable: function() { @@ -287,7 +288,7 @@ define( [ "jquery", "./forms/textinput" ], function( jQuery ) { disable: function() { return this._setOption( "disabled", true ); - }, + }, destroy: function() { var self = this, @@ -297,13 +298,13 @@ define( [ "jquery", "./forms/textinput" ], function( jQuery ) { if ( o.enhance ) { wrapper.parentNode.removeChild( wrapper ); } - self._toggleFilterableItems( self._getFilterableItems(), false, false ); + self._toggleFilterableItems( self._getFilterableItems(), false, false ); self._destroy(); } }, $.mobile.behaviors.addFirstLastClasses ) ); - $.mobile.filterbar.initSelector = ':jqmData(filter="true")'; + $.mobile.filterbar.initSelector = ":jqmData(filter='true')"; //auto self-init widgets $.mobile._enhancer.add( "mobile.filterbar" ); From aa7442c6d6fedcb31f962184db8f5fb45b44fb21 Mon Sep 17 00:00:00 2001 From: frequent Date: Wed, 10 Jul 2013 21:53:53 +0200 Subject: [PATCH 03/13] added missing caching tests from listview widget --- tests/integration/filter/filter_core.js | 42 +++++++++++++++++++++++++ tests/integration/filter/index.html | 14 +++++++++ 2 files changed, 56 insertions(+) diff --git a/tests/integration/filter/filter_core.js b/tests/integration/filter/filter_core.js index 6af26edeeef..99a76f0e0a6 100644 --- a/tests/integration/filter/filter_core.js +++ b/tests/integration/filter/filter_core.js @@ -404,6 +404,48 @@ ]); }); + module( "Caching" ); + + asyncTest( "list filter is inset from prototype options value", function() { + $.mobile.filterbar.prototype.options.inset = true; + $("#list-inset-filter-prototype").page(); + + $.testHelper.pageSequence([ + function() { + $.mobile.changePage("#list-inset-filter-prototype"); + }, + + function( timedOut) { + ok( !timedOut ); + console.log( $.mobile.activePage ); + console.log( $.mobile.activePage.find("div.ui-filter-inset")); + deepEqual( $.mobile.activePage.find("div.ui-filter-inset").length, 1, "div is inset"); + window.history.back(); + }, + + start + ]); + }); + + asyncTest( "list filter is inset from data attr value", function() { + $.mobile.listview.prototype.options.inset = false; + $("#list-inset-filter-data-attr").page(); + + $.testHelper.pageSequence([ + function() { + $.mobile.changePage("#list-inset-filter-data-attr"); + }, + + function( timedOut) { + ok( !timedOut ); + deepEqual( $.mobile.activePage.find("div.ui-filter-inset").length, 1, "div is inset"); + window.history.back(); + }, + + start + ]); + }); + module( "Filter Widget Configuration" ); asyncTest( "Custom id and classes are set on filter", function () { diff --git a/tests/integration/filter/index.html b/tests/integration/filter/index.html index 7ef9bd74d5b..84a56f87b7f 100644 --- a/tests/integration/filter/index.html +++ b/tests/integration/filter/index.html @@ -446,5 +446,19 @@

Filtering different elements: span

+
+ +
+ +
+ +
+ From 3c406184b2fa805d5ec4a3f588c58b4fb92ec247 Mon Sep 17 00:00:00 2001 From: frequent Date: Wed, 10 Jul 2013 22:48:57 +0200 Subject: [PATCH 04/13] adding filter.css/set ui-screen-hidden/display:none !important --- tests/integration/filter/index.html | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/integration/filter/index.html b/tests/integration/filter/index.html index 84a56f87b7f..89dd211890a 100644 --- a/tests/integration/filter/index.html +++ b/tests/integration/filter/index.html @@ -42,6 +42,35 @@ + + + From 4991fbc7d8403661237225f992fbcab232506a15 Mon Sep 17 00:00:00 2001 From: frequent Date: Wed, 10 Jul 2013 23:09:53 +0200 Subject: [PATCH 05/13] removed listview filter extension js/css/tests --- css/structure/jquery.mobile.filterbar.css | 4 + css/structure/jquery.mobile.listview.css | 25 -- tests/integration/filter/index.html | 3 - tests/integration/listview/index.html | 113 ------- tests/integration/listview/listview_core.js | 354 -------------------- 5 files changed, 4 insertions(+), 495 deletions(-) diff --git a/css/structure/jquery.mobile.filterbar.css b/css/structure/jquery.mobile.filterbar.css index 1000b8a55d9..6a400d8634e 100644 --- a/css/structure/jquery.mobile.filterbar.css +++ b/css/structure/jquery.mobile.filterbar.css @@ -20,6 +20,10 @@ 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/tests/integration/filter/index.html b/tests/integration/filter/index.html index 89dd211890a..1c98947550b 100644 --- a/tests/integration/filter/index.html +++ b/tests/integration/filter/index.html @@ -66,9 +66,6 @@ width: auto; display: block; } - .ui-screen-hidden { - display: none !important; - } diff --git a/tests/integration/listview/index.html b/tests/integration/listview/index.html index 08e2fedc31f..6f578cdb08d 100644 --- a/tests/integration/listview/index.html +++ b/tests/integration/listview/index.html @@ -22,7 +22,6 @@ [ "jquery.mobile.navigation", "widgets/listview", - "widgets/listview.filter", "widgets/listview.autodividers", "widgets/toolbar", "widgets/collapsible", @@ -192,103 +191,6 @@

Autodivider Selector Test

- -
-
-

Split List View

-
-
-
    -
  • a is for aquaman
  • -
  • b is for batman
  • -
  • c is for catwoman
  • -
  • d is for darkwing
  • -
-
-
- - -
-
-

Filtered List View

-
-
-
    -
  • a is for aquaman
  • -
  • b is for batman
  • -
  • c is for catwoman
  • -
  • d is for darkwing
  • -
-
-
- - -
-
-

Split List View

-
-
-
    -
  • a
  • -
  • a is for aquaman
  • -
  • b
  • -
  • b is for batman
  • -
  • c
  • -
  • c is for catwoman
  • -
  • d
  • -
  • d is for darkwing
  • -
-
-
- - -
-
-

Inset Filter List View

-
-
-
    -
  • a is for aquaman
  • -
  • b is for batman
  • -
  • c is for catwoman
  • -
  • d is for darkwing
  • -
-
-
- - -
-
-

Reveal Listview

-
-
-
    -
  • Acura
  • -
  • Audi
  • -
  • BMW
  • -
  • Cadillac
  • -
  • Chrysler
  • -
  • Dodge
  • -
  • Ferrari
  • -
  • Ford
  • -
  • GMC
  • -
  • Honda
  • -
  • Hyundai
  • -
  • Infiniti
  • -
  • Jeep
  • -
  • Kia
  • -
  • Lexus
  • -
  • Mini
  • -
  • Nissan
  • -
  • Porsche
  • -
  • Subaru
  • -
  • Toyota
  • -
  • Volkswagon
  • -
  • Volvo
  • -
-
-
-
    @@ -352,21 +254,6 @@

    Basic List View

    - -
    -
      -
    • foo
    • -
    • bar
    • -
    -
    - -
    -
      -
    • foo
    • -
    • bar
    • -
    -
    -
      diff --git a/tests/integration/listview/listview_core.js b/tests/integration/listview/listview_core.js index db9b47fbe36..71b639a8b1b 100755 --- a/tests/integration/listview/listview_core.js +++ b/tests/integration/listview/listview_core.js @@ -424,89 +424,6 @@ ]); }); - module( "Search Filter" ); - - var searchFilterId = "#search-filter-test"; - - asyncTest( "Filter downs results when the user enters information", function() { - var $searchPage = $(searchFilterId); - $.testHelper.pageSequence([ - function() { - $.mobile.changePage(searchFilterId); - }, - - function() { - $searchPage.find('input').val('at'); - $searchPage.find('input').trigger('change'); - - deepEqual($searchPage.find('li.ui-screen-hidden').length, 2); - start(); - } - ]); - }); - - asyncTest( "Redisplay results when user removes values", function() { - var $searchPage = $(searchFilterId); - $.testHelper.pageSequence([ - function() { - $.mobile.changePage(searchFilterId); - }, - - function() { - $searchPage.find('input').val('a'); - $searchPage.find('input').trigger('change'); - - deepEqual($searchPage.find("li[style^='display: none;']").length, 0); - start(); - } - ]); - }); - - asyncTest( "Filter works fine with \\W- or regexp-special-characters", function() { - var $searchPage = $(searchFilterId); - $.testHelper.pageSequence([ - function() { - $.mobile.changePage(searchFilterId); - }, - - function() { - $searchPage.find('input').val('*'); - $searchPage.find('input').trigger('change'); - - deepEqual($searchPage.find('li.ui-screen-hidden').length, 4); - start(); - } - ]); - }); - - asyncTest( "event listviewbeforefilter firing", function() { - var $searchPage = $( searchFilterId ); - $.testHelper.pageSequence([ - function() { - $.mobile.changePage( searchFilterId ); - }, - - function() { - var beforeFilterCount = 0; - $searchPage.on( "listviewbeforefilter", function( e ) { - beforeFilterCount += 1; - }); - $searchPage.find( 'input' ).val( "a" ); - $searchPage.find( 'input' ).trigger('input'); - $searchPage.find( 'input' ).trigger('keyup'); - $searchPage.find( 'input' ).trigger('change'); - equal( beforeFilterCount, 1, "listviewbeforefilter should fire only once for the same value" ); - - $searchPage.find( 'input' ).val( "ab" ); - $searchPage.find( 'input' ).trigger('input'); - $searchPage.find( 'input' ).trigger('keyup'); - equal( beforeFilterCount, 2, "listviewbeforefilter should fire twice since value has changed" ); - - start(); - } - ]); - }); - test( "Refresh applies thumb styling", function(){ var ul = $('.ui-page-active ul'); @@ -516,239 +433,6 @@ ok(ul.find("#fiz").hasClass("ui-li-has-thumb")); }); - asyncTest( "Filter downs results and dividers when the user enters information", function() { - var $searchPage = $("#search-filter-with-dividers-test"); - $.testHelper.pageSequence([ - function() { - $.mobile.changePage("#search-filter-with-dividers-test"); - }, - - // wait for the page to become active/enhanced - function(){ - $searchPage.find('input').val('at'); - $searchPage.find('input').trigger('change'); - setTimeout(function() { - //there should be four hidden list entries - deepEqual($searchPage.find('li.ui-screen-hidden').length, 4); - - //there should be two list entries that are list dividers and hidden - deepEqual($searchPage.find('li.ui-screen-hidden:jqmData(role=list-divider)').length, 2); - - //there should be two list entries that are not list dividers and hidden - deepEqual($searchPage.find('li.ui-screen-hidden:not(:jqmData(role=list-divider))').length, 2); - start(); - }, 1000); - } - ]); - }); - - asyncTest( "Redisplay results when user removes values", function() { - $.testHelper.pageSequence([ - function() { - $.mobile.changePage("#search-filter-with-dividers-test"); - }, - - function() { - $('.ui-page-active input').val('a'); - $('.ui-page-active input').trigger('change'); - - setTimeout(function() { - deepEqual($('.ui-page-active input').val(), 'a'); - deepEqual($('.ui-page-active li[style^="display: none;"]').length, 0); - start(); - }, 1000); - } - ]); - }); - - asyncTest( "Dividers are hidden when preceding hidden rows and shown when preceding shown rows", function () { - $.testHelper.pageSequence([ - function() { - $.mobile.changePage("#search-filter-with-dividers-test"); - }, - - function() { - var $page = $('.ui-page-active'); - - $page.find('input').val('at'); - $page.find('input').trigger('change'); - - setTimeout(function() { - deepEqual($page.find('li:jqmData(role=list-divider):hidden').length, 2); - deepEqual($page.find('li:jqmData(role=list-divider):hidden + li:not(:jqmData(role=list-divider)):hidden').length, 2); - deepEqual($page.find('li:jqmData(role=list-divider):not(:hidden) + li:not(:jqmData(role=list-divider)):not(:hidden)').length, 2); - start(); - }, 1000); - } - ]); - }); - - asyncTest( "Inset List View should refresh corner classes after filtering", 4 * 2, function () { - var checkClasses = function() { - var $page = $( ".ui-page-active" ), - $li = $page.find( "li:visible" ); - ok($li.first().hasClass( "ui-first-child" ), $li.length+" li elements: First visible element should have class ui-first-child"); - ok($li.last().hasClass( "ui-last-child" ), $li.length+" li elements: Last visible element should have class ui-last-child"); - }; - - $.testHelper.pageSequence([ - function() { - $.mobile.changePage("#search-filter-inset-test"); - }, - - function() { - var $page = $('.ui-page-active'); - $.testHelper.sequence([ - function() { - checkClasses(); - - $page.find('input').val('man'); - $page.find('input').trigger('change'); - }, - - function() { - checkClasses(); - - $page.find('input').val('at'); - $page.find('input').trigger('change'); - }, - - function() { - checkClasses(); - - $page.find('input').val('catwoman'); - $page.find('input').trigger('change'); - }, - - function() { - checkClasses(); - start(); - } - ], 50); - } - ]); - }); - - module( "Custom search filter", { - setup: function() { - var self = this; - this._refreshCornersCount = 0; - this._refreshCornersFn = $.mobile.listview.prototype._addFirstLastClasses; - - this.startTest = function() { - return this._refreshCornersCount === 1; - }; - - // _refreshCorners is the last method called in the filter loop - // so we count the number of times _refreshCorners gets invoked to stop the test - $.mobile.listview.prototype._addFirstLastClasses = function() { - self._refreshCornersCount += 1; - self._refreshCornersFn.apply( this, arguments ); - if ( self.startTest() ) { - start(); - } - } - }, - teardown: function() { - $.mobile.listview.prototype._refreshCorners = this._refreshCornersFn; - } - }); - - asyncTest( "Custom filterCallback should cause iteration on all list elements", function(){ - var listPage = $( "#search-customfilter-test" ), - filterCallbackCount = 0, - expectedCount = 2 * listPage.find("li").length; - expect( 1 ); - - this.startTest = function() { - if ( this._refreshCornersCount === 3 ) { - equal( filterCallbackCount, expectedCount, "filterCallback should be called exactly "+ expectedCount +" times" ); - } - return this._refreshCornersCount === 3; - } - - $.testHelper.pageSequence( [ - function(){ - //reset for relative url refs - $.mobile.changePage( home ); - }, - - function() { - $.mobile.changePage( "#search-customfilter-test" ); - }, - - function() { - // set the listview instance callback - listPage.find( "ul" ).listview( "option", "filterCallback", function( text, searchValue, item ) { - filterCallbackCount += 1; - - return text.toString().toLowerCase().indexOf( searchValue ) === -1; - }); - - // trigger a change in the search filter - listPage.find( "input" ).val( "at" ).trigger( "change" ); - listPage.find( "input" ).val( "atw" ).trigger( "change" ); - - } - ]); - }); - - asyncTest( "filterCallback can be altered after widget creation", function(){ - var listPage = $( "#search-customfilter-test" ); - expect( listPage.find("li").length ); - - $.testHelper.pageSequence( [ - function(){ - //reset for relative url refs - $.mobile.changePage( home ); - }, - - function() { - $.mobile.changePage( "#search-customfilter-test" ); - }, - - function() { - // set the listview instance callback - listPage.find( "ul" ).listview( "option", "filterCallback", function() { - ok( true, "custom callback invoked" ); - }); - - // trigger a change in the search filter - listPage.find( "input" ).val( "foo" ).trigger( "change" ); - } - ]); - }); - - module( "Search Filter with filterReveal==true" ); - - asyncTest( "Filter downs results when the user enters information", 3, function() { - var $searchPage = $( "#search-filter-reveal-test" ); - $.testHelper.pageSequence([ - function() { - $.mobile.changePage( $searchPage ); - }, - - function() { - deepEqual( $searchPage.find( 'li.ui-screen-hidden' ).length, 22); - }, - - function() { - $searchPage.find( 'input' ).val( 'a' ); - $searchPage.find( 'input' ).trigger('change'); - - deepEqual( $searchPage.find('li.ui-screen-hidden').length, 11); - }, - - function() { - $searchPage.find( 'input' ).val( '' ); - $searchPage.find( 'input' ).trigger('change'); - - deepEqual( $searchPage.find('li.ui-screen-hidden').length, 22); - start(); - } - ]); - }); - module( "Programmatically generated list items", { setup: function(){ var label, item, @@ -877,44 +561,6 @@ ]); }); - asyncTest( "list filter is inset from prototype options value", function() { - $.mobile.listview.prototype.options.inset = true; - $("#list-inset-filter-prototype").page(); - - $.testHelper.pageSequence([ - function() { - $.mobile.changePage("#list-inset-filter-prototype"); - }, - - function( timedOut) { - ok( !timedOut ); - deepEqual( $.mobile.activePage.find("form.ui-listview-filter-inset").length, 1, "form is inset"); - window.history.back(); - }, - - start - ]); - }); - - asyncTest( "list filter is inset from data attr value", function() { - $.mobile.listview.prototype.options.inset = false; - $("#list-inset-filter-data-attr").page(); - - $.testHelper.pageSequence([ - function() { - $.mobile.changePage("#list-inset-filter-data-attr"); - }, - - function( timedOut) { - ok( !timedOut ); - deepEqual( $.mobile.activePage.find("form.ui-listview-filter-inset").length, 1, "form is inset"); - window.history.back(); - }, - - start - ]); - }); - asyncTest( "split list items respect the icon", function() { $.testHelper.pageSequence([ function() { From 82011dbcc2d470a85260d695b328495784f2d7f5 Mon Sep 17 00:00:00 2001 From: frequent Date: Thu, 11 Jul 2013 22:15:34 +0200 Subject: [PATCH 06/13] replaced self, added _delay --- js/widgets/filter.js | 91 +++++++++++++++++++------------------------- 1 file changed, 40 insertions(+), 51 deletions(-) diff --git a/js/widgets/filter.js b/js/widgets/filter.js index b42945dac05..a14806ee205 100644 --- a/js/widgets/filter.js +++ b/js/widgets/filter.js @@ -7,7 +7,6 @@ define( [ "jquery", "./forms/textinput" ], function( jQuery ) { //>>excludeEnd("jqmBuildExclude"); (function( $, undefined ) { - "use strict"; // TODO rename filterCallback/deprecate and default to the item itself as the first argument var defaultfilterCallback = function( text, searchValue /*, item */) { @@ -31,9 +30,8 @@ define( [ "jquery", "./forms/textinput" ], function( jQuery ) { }, _onKeyUp: function() { - var self = this, - search = self._search[ 0 ], - o = self.options, + var search = this._search[ 0 ], + o = this.options, getAttrFixed = $.mobile.getAttribute, val = search.value.toLowerCase(), lastval = getAttrFixed( search, "lastval", true ) + ""; @@ -47,21 +45,19 @@ define( [ "jquery", "./forms/textinput" ], function( jQuery ) { window.clearTimeout(o.timer); } - o.timer = window.setTimeout(function() { + o.timer = this._delay(function() { + this._trigger( "beforefilter", "beforefilter", { input: search } ); - self._trigger( "beforefilter", "beforefilter", { input: search } ); - // Change val as lastval for next execution search.setAttribute( "data-" + $.mobile.ns + "lastval" , val ); - - self._filterItems( search, val, lastval ); - }, 250); + + this._filterItems( search, val, lastval ); + }, 250 ) }, _getFilterableItems: function() { - var self = this, - el = self.element, - o = self.options, + var el = this.element, + o = this.options, items = []; if (typeof o.selector === "string") { @@ -73,11 +69,10 @@ define( [ "jquery", "./forms/textinput" ], function( jQuery ) { }, _setFilterableItems: function(val, lastval) { - var self = this, - o = self.options, + var o = this.options, filterItems = [], isCustomfilterCallback = o.filterCallback !== defaultfilterCallback, - _getFilterableItems = self._getFilterableItems(); + _getFilterableItems = this._getFilterableItems(); if ( isCustomfilterCallback || val.length < lastval.length || val.indexOf( lastval ) !== 0 ) { @@ -96,18 +91,17 @@ define( [ "jquery", "./forms/textinput" ], function( jQuery ) { }, _filterItems: function( search, val, lastval ){ - var self = this, - o = self.options, + var o = this.options, getAttrFixed = $.mobile.getAttribute, - filterItems = self._setFilterableItems(val, lastval), - _getFilterableItems = self._getFilterableItems(), + filterItems = this._setFilterableItems(val, lastval), + _getFilterableItems = this._getFilterableItems(), childItems = false, itemtext = "", item, - select = self.element.parents( ".ui-select" ), + select = this.element.parents( ".ui-select" ), i; - self._setOption( "timer", undefined ); + this._setOption( "timer", undefined ); if ( val ) { @@ -137,12 +131,12 @@ define( [ "jquery", "./forms/textinput" ], function( jQuery ) { } } - self._toggleFilterableItems( filterItems, select, o.filterReveal , true); + this._toggleFilterableItems( filterItems, select, o.filterReveal , true); } else { - self._toggleFilterableItems( filterItems, select, o.filterReveal ); + this._toggleFilterableItems( filterItems, select, o.filterReveal ); } - self._addFirstLastClasses( _getFilterableItems, self._getVisibles( _getFilterableItems, false ), false ); + this._addFirstLastClasses( _getFilterableItems, this._getVisibles( _getFilterableItems, false ), false ); }, _toggleFilterableItems: function( filterItems, select, reveal, isVal ) { @@ -176,13 +170,12 @@ define( [ "jquery", "./forms/textinput" ], function( jQuery ) { }, _enhance: function () { - var self = this, - el = this.element, - o = self.options, + var el = this.element, + o = this.options, wrapper = $( "
      ", { "class": o.classes + " ui-filter ", "role": "search", - "id" : o.id || "ui-filter-" + self.uuid + "id" : o.id || "ui-filter-" + this.uuid }), search = $( "", { placeholder: o.filterPlaceholder @@ -208,19 +201,18 @@ define( [ "jquery", "./forms/textinput" ], function( jQuery ) { }, _create: function() { - var self = this, - o = self.options, + var o = this.options, search, - items = self._getFilterableItems(); + items = this._getFilterableItems(); if ( o.filterReveal ) { items.addClass( "ui-screen-hidden" ); } - self._setOption( "timer", undefined ); + this._setOption( "timer", undefined ); if (o.enhance) { - search = self._enhance(); + search = this._enhance(); } else { // NOTE: DIY requires data-id, otherwise how do we find the search // input. We could always wrap the filterable element (e.g. ul) in @@ -229,9 +221,9 @@ define( [ "jquery", "./forms/textinput" ], function( jQuery ) { search = $( "#" + o.id ).find( "input" ); } - self._on( search, { keyup: "_onKeyUp", change: "_onKeyUp", input: "_onKeyUp" } ); + this._on( search, { keyup: "_onKeyUp", change: "_onKeyUp", input: "_onKeyUp" } ); - $.extend( self, { + $.extend( this, { _search: search }); @@ -244,26 +236,24 @@ define( [ "jquery", "./forms/textinput" ], function( jQuery ) { // "_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. - self._addFirstLastClasses( items, self._getVisibles( items, true ), true ); + this._addFirstLastClasses( items, this._getVisibles( items, true ), true ); }, _setOptions: function( options ) { - var self = this, - key; + var key; for ( key in options ) { if(options.hasOwnProperty(key)) { - self._setOption( key, options[ key ] ); + this._setOption( key, options[ key ] ); } } - return self; + return this; }, _setOption: function( key, value ) { - var self = this, - o = self.options, - wrapper = document.getElementById( o.id || "ui-filter-" + self.uuid ), + var o = this.options, + wrapper = document.getElementById( o.id || "ui-filter-" + this.uuid ), $input = $( wrapper ).find( "input" ); // always update @@ -271,11 +261,11 @@ define( [ "jquery", "./forms/textinput" ], function( jQuery ) { if ( key === "disabled" ) { $input - .toggleClass( self.widgetFullName + "-disabled ui-state-disabled", !!value ) + .toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value ) .attr( "aria-disabled", value ) .textinput( value ? "disable" : "enable" ); } - return self; + return this; }, widget: function() { @@ -291,15 +281,14 @@ define( [ "jquery", "./forms/textinput" ], function( jQuery ) { }, destroy: function() { - var self = this, - o = self.options, - wrapper = document.getElementById( o.id || "ui-filter-" + self.uuid ); + var o = this.options, + wrapper = document.getElementById( o.id || "ui-filter-" + this.uuid ); if ( o.enhance ) { wrapper.parentNode.removeChild( wrapper ); } - self._toggleFilterableItems( self._getFilterableItems(), false, false ); - self._destroy(); + this._toggleFilterableItems( this._getFilterableItems(), false, false ); + this._destroy(); } }, $.mobile.behaviors.addFirstLastClasses ) ); From 3ce516bf64080cf50eac10bd4fe51b117cb6f866 Mon Sep 17 00:00:00 2001 From: frequent Date: Thu, 11 Jul 2013 23:14:47 +0200 Subject: [PATCH 07/13] removed id option, changed to wrapperClass --- js/widgets/filter.js | 33 ++++++++++++------------- tests/integration/filter/filter_core.js | 7 ++---- tests/integration/filter/index.html | 5 +++- 3 files changed, 22 insertions(+), 23 deletions(-) diff --git a/js/widgets/filter.js b/js/widgets/filter.js index a14806ee205..01d11597ada 100644 --- a/js/widgets/filter.js +++ b/js/widgets/filter.js @@ -13,17 +13,16 @@ define( [ "jquery", "./forms/textinput" ], function( jQuery ) { return text.toString().toLowerCase().indexOf( searchValue ) === -1; }; - $.widget("mobile.filterbar", $.mobile.widget, $.extend( { + $.widget( "mobile.filterbar", $.extend( { options: { - filterTheme: "a", + theme: "a", filterPlaceholder: "Filter items...", filterReveal: false, filterCallback: defaultfilterCallback, - classes: "", - id: null, + wrapperClass: "", inset: false, - enhance: true, + enhanced: false, target: null, mini: false, selector: null @@ -173,9 +172,9 @@ define( [ "jquery", "./forms/textinput" ], function( jQuery ) { var el = this.element, o = this.options, wrapper = $( "
      ", { - "class": o.classes + " ui-filter ", + "class": o.wrapperClass + " ui-filter ", "role": "search", - "id" : o.id || "ui-filter-" + this.uuid + "id" : "ui-filter-" + this.uuid }), search = $( "", { placeholder: o.filterPlaceholder @@ -211,17 +210,17 @@ define( [ "jquery", "./forms/textinput" ], function( jQuery ) { this._setOption( "timer", undefined ); - if (o.enhance) { + if ( o.enhanced === false ) { search = this._enhance(); } else { - // NOTE: DIY requires data-id, otherwise how do we find the search - // input. We could always wrap the filterable element (e.g. ul) in - // ui-filter as well, but I'm not sure I want to move elements around - // that much. - search = $( "#" + o.id ).find( "input" ); + search = $( "#ui-filter-" + this.uuid ).find( "input" ); } - this._on( search, { keyup: "_onKeyUp", change: "_onKeyUp", input: "_onKeyUp" } ); + this._on( search, { + keyup: "_onKeyUp", + change: "_onKeyUp", + input: "_onKeyUp" + }); $.extend( this, { _search: search @@ -253,7 +252,7 @@ define( [ "jquery", "./forms/textinput" ], function( jQuery ) { _setOption: function( key, value ) { var o = this.options, - wrapper = document.getElementById( o.id || "ui-filter-" + this.uuid ), + wrapper = document.getElementById( "ui-filter-" + this.uuid ), $input = $( wrapper ).find( "input" ); // always update @@ -282,9 +281,9 @@ define( [ "jquery", "./forms/textinput" ], function( jQuery ) { destroy: function() { var o = this.options, - wrapper = document.getElementById( o.id || "ui-filter-" + this.uuid ); + wrapper = document.getElementById( "ui-filter-" + this.uuid ); - if ( o.enhance ) { + if ( o.enhanced === false ) { wrapper.parentNode.removeChild( wrapper ); } this._toggleFilterableItems( this._getFilterableItems(), false, false ); diff --git a/tests/integration/filter/filter_core.js b/tests/integration/filter/filter_core.js index 99a76f0e0a6..25d70bf6a4a 100644 --- a/tests/integration/filter/filter_core.js +++ b/tests/integration/filter/filter_core.js @@ -224,7 +224,6 @@ $page.find('input').val('at'); $page.find('input').trigger('change'); - setTimeout(function() { deepEqual($page.find('li:jqmData(role=list-divider):hidden').length, 2); deepEqual($page.find('li:jqmData(role=list-divider):hidden + li:not(:jqmData(role=list-divider)):hidden').length, 2); @@ -417,8 +416,6 @@ function( timedOut) { ok( !timedOut ); - console.log( $.mobile.activePage ); - console.log( $.mobile.activePage.find("div.ui-filter-inset")); deepEqual( $.mobile.activePage.find("div.ui-filter-inset").length, 1, "div is inset"); window.history.back(); }, @@ -463,8 +460,8 @@ $filter = $page.find( ".ui-filter" ), $list = $page.find( "ul" ); - ok($filter.hasClass( "baz" ), "filter element has custom classed set by user"); - ok($filter.attr( "id" ) === "foo", "filter has custom id"); + ok($filter.is( ".baz" ), "filter element has custom classed set by user"); + //removed ok($filter.attr( "id" ) === "foo", "filter has custom id"); $list.filterbar("destroy"); diff --git a/tests/integration/filter/index.html b/tests/integration/filter/index.html index 1c98947550b..a2839b69e61 100644 --- a/tests/integration/filter/index.html +++ b/tests/integration/filter/index.html @@ -66,6 +66,9 @@ width: auto; display: block; } + .ui-screen-hidden { + display: none !important; + } @@ -195,7 +198,7 @@

      Reveal Listview

      Using custom id and classes

      -
        +
        • Acura
        • Audi
        • BMW
        • From a472a4020289e20649da0a5a3ffbec4facda2fd2 Mon Sep 17 00:00:00 2001 From: frequent Date: Thu, 11 Jul 2013 23:35:00 +0200 Subject: [PATCH 08/13] remaining fixes execpt widget method --- js/widgets/filter.js | 41 ++++++++++------------------------------- 1 file changed, 10 insertions(+), 31 deletions(-) diff --git a/js/widgets/filter.js b/js/widgets/filter.js index 01d11597ada..40294c68484 100644 --- a/js/widgets/filter.js +++ b/js/widgets/filter.js @@ -168,7 +168,7 @@ define( [ "jquery", "./forms/textinput" ], function( jQuery ) { } }, - _enhance: function () { + _enhance: function ( items ) { var el = this.element, o = this.options, wrapper = $( "
          ", { @@ -190,6 +190,10 @@ define( [ "jquery", "./forms/textinput" ], function( jQuery ) { wrapper.addClass( "ui-filter-inset" ); } + if ( o.filterReveal ) { + items.addClass( "ui-screen-hidden" ); + } + if ( typeof o.target === "string" ) { wrapper.prependTo( $( "." + o.target + "" ) ); } else { @@ -200,18 +204,14 @@ define( [ "jquery", "./forms/textinput" ], function( jQuery ) { }, _create: function() { - var o = this.options, - search, + var search, + o = this.options, items = this._getFilterableItems(); - - if ( o.filterReveal ) { - items.addClass( "ui-screen-hidden" ); - } this._setOption( "timer", undefined ); if ( o.enhanced === false ) { - search = this._enhance(); + search = this._enhance( items ); } else { search = $( "#ui-filter-" + this.uuid ).find( "input" ); } @@ -237,18 +237,6 @@ define( [ "jquery", "./forms/textinput" ], function( jQuery ) { // when setting up the parent listview. this._addFirstLastClasses( items, this._getVisibles( items, true ), true ); }, - - _setOptions: function( options ) { - var key; - - for ( key in options ) { - if(options.hasOwnProperty(key)) { - this._setOption( key, options[ key ] ); - } - } - - return this; - }, _setOption: function( key, value ) { var o = this.options, @@ -271,23 +259,14 @@ define( [ "jquery", "./forms/textinput" ], function( jQuery ) { return this.element; }, - enable: function() { - return this._setOption( "disabled", false ); - }, - - disable: function() { - return this._setOption( "disabled", true ); - }, - - destroy: function() { + _destroy: function() { var o = this.options, wrapper = document.getElementById( "ui-filter-" + this.uuid ); - + if ( o.enhanced === false ) { wrapper.parentNode.removeChild( wrapper ); } this._toggleFilterableItems( this._getFilterableItems(), false, false ); - this._destroy(); } }, $.mobile.behaviors.addFirstLastClasses ) ); From ed8002829e0381acd02b2d6b638886a4c6df13f7 Mon Sep 17 00:00:00 2001 From: frequent Date: Fri, 12 Jul 2013 19:21:57 +0200 Subject: [PATCH 09/13] linting & switched to o.enhanced --- js/widgets/filter.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js/widgets/filter.js b/js/widgets/filter.js index 40294c68484..3524df9ac81 100644 --- a/js/widgets/filter.js +++ b/js/widgets/filter.js @@ -51,7 +51,7 @@ define( [ "jquery", "./forms/textinput" ], function( jQuery ) { search.setAttribute( "data-" + $.mobile.ns + "lastval" , val ); this._filterItems( search, val, lastval ); - }, 250 ) + }, 250 ); }, _getFilterableItems: function() { @@ -210,7 +210,7 @@ define( [ "jquery", "./forms/textinput" ], function( jQuery ) { this._setOption( "timer", undefined ); - if ( o.enhanced === false ) { + if ( !o.enhanced ) { search = this._enhance( items ); } else { search = $( "#ui-filter-" + this.uuid ).find( "input" ); @@ -263,7 +263,7 @@ define( [ "jquery", "./forms/textinput" ], function( jQuery ) { var o = this.options, wrapper = document.getElementById( "ui-filter-" + this.uuid ); - if ( o.enhanced === false ) { + if ( !o.enhanced ) { wrapper.parentNode.removeChild( wrapper ); } this._toggleFilterableItems( this._getFilterableItems(), false, false ); From be0dc65767202d36261b9b2af5462b44db6a6895 Mon Sep 17 00:00:00 2001 From: frequent Date: Mon, 15 Jul 2013 01:50:43 +0200 Subject: [PATCH 10/13] trying to fix test for filtering select options --- tests/integration/filter/filter_core.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/integration/filter/filter_core.js b/tests/integration/filter/filter_core.js index 25d70bf6a4a..16671c70ee0 100644 --- a/tests/integration/filter/filter_core.js +++ b/tests/integration/filter/filter_core.js @@ -720,6 +720,7 @@ $page.find('input').val('a'); $page.find('input').trigger('change'); setTimeout(function() { + deepEqual( $select.find('.ui-screen-hidden').length, 9, @@ -753,26 +754,30 @@ }, function() { - var $page = $( ".ui-page-active" ), + var $options, + $page = $( ".ui-page-active" ), $filter = $page.find( ".ui-filter" ), - $select = $page.find( ".ui-select" ); + $select = $page.find( ".ui-select select" ); // filter $page.find('input').val('this goes'); $page.find('input').trigger('change'); + setTimeout(function() { + $options = $select.find("option.ui-screen-hidden"); deepEqual( - $select.find('.ui-screen-hidden').length, + $options.length, 9, "Filtering select options by option text" ); - + // clear $page.find('input').val(''); $page.find('input').trigger('change'); setTimeout(function() { + $options = $select.find("option.ui-screen-hidden"); deepEqual( - $select.find('.ui-screen-hidden').length, + $options.length, 0, "Removing filter value shows all select options again" ); From 75e229980dea55e5d7558f77fca23235d189dc2c Mon Sep 17 00:00:00 2001 From: frequent Date: Mon, 15 Jul 2013 20:47:53 +0200 Subject: [PATCH 11/13] switched to o.wrapperClass to identify pre-enhanced markup --- js/widgets/filter.js | 4 ++-- tests/integration/filter/filter_core.js | 2 +- tests/integration/filter/index.html | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/js/widgets/filter.js b/js/widgets/filter.js index 3524df9ac81..23fb6813b23 100644 --- a/js/widgets/filter.js +++ b/js/widgets/filter.js @@ -172,7 +172,7 @@ define( [ "jquery", "./forms/textinput" ], function( jQuery ) { var el = this.element, o = this.options, wrapper = $( "
          ", { - "class": o.wrapperClass + " ui-filter ", + "class": "ui-filter " + o.wrapperClass, "role": "search", "id" : "ui-filter-" + this.uuid }), @@ -213,7 +213,7 @@ define( [ "jquery", "./forms/textinput" ], function( jQuery ) { if ( !o.enhanced ) { search = this._enhance( items ); } else { - search = $( "#ui-filter-" + this.uuid ).find( "input" ); + search = $( "." + o.wrapperClass ).find( "input" ); } this._on( search, { diff --git a/tests/integration/filter/filter_core.js b/tests/integration/filter/filter_core.js index 16671c70ee0..0eb55250b38 100644 --- a/tests/integration/filter/filter_core.js +++ b/tests/integration/filter/filter_core.js @@ -524,7 +524,7 @@ ]); }); - asyncTest( "Filter can be set pre-enhanced (if data-id is provided)", function () { + asyncTest( "Filter can be set pre-enhanced (if wrapper class is provided)", function () { $.testHelper.pageSequence( [ function(){ $.mobile.changePage( home ); diff --git a/tests/integration/filter/index.html b/tests/integration/filter/index.html index a2839b69e61..f34bf97f61b 100644 --- a/tests/integration/filter/index.html +++ b/tests/integration/filter/index.html @@ -264,14 +264,14 @@

          Using pre-enhanced filter

          - -
            +
            • Acura
            • Audi
            • BMW
            • From c337959ace7514bd286245c4b3459b7f01fc4172 Mon Sep 17 00:00:00 2001 From: frequent Date: Mon, 15 Jul 2013 23:31:32 +0200 Subject: [PATCH 12/13] replaced all references to listview filter with filter widget --- css/structure/jquery.mobile.structure.css | 1 + demos/_assets/css/jqm-demos.css | 34 ++-- demos/_assets/js/jqm-demos.js | 16 +- demos/index.php | 6 +- demos/nav-examples.php | 5 + demos/nav-widgets.php | 4 +- .../autocomplete/autocomplete-remote.php | 10 +- demos/widgets/autocomplete/index.php | 8 +- js/index.php | 2 +- js/jquery.mobile.js | 2 +- js/widgets/listview.filter.js | 156 ------------------ 11 files changed, 49 insertions(+), 195 deletions(-) delete mode 100644 js/widgets/listview.filter.js 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..37714cffe29 100644 --- a/demos/_assets/css/jqm-demos.css +++ b/demos/_assets/css/jqm-demos.css @@ -119,11 +119,11 @@ 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-demos .jqm-search > .ui-filter .ui-input-search { margin: 4px .3125em; } .jqm-search > .jqm-list { @@ -434,19 +434,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 +454,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 +464,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 +472,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 +625,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
          -
          diff --git a/demos/nav-examples.php b/demos/nav-examples.php index b0be59a8aab..de72c606a73 100644 --- a/demos/nav-examples.php +++ b/demos/nav-examples.php @@ -6,6 +6,11 @@
        • Dynamic controlgroup
        • +
        • Listviews
        • Grid Listview
        • diff --git a/demos/nav-widgets.php b/demos/nav-widgets.php index 31666d8d3fe..1fed107699b 100644 --- a/demos/nav-widgets.php +++ b/demos/nav-widgets.php @@ -2,7 +2,7 @@
        • AJAX Navigation
        • -
        • Autocomplete
        • +
        • Autocomplete
        • Buttons
        • @@ -12,6 +12,8 @@
        • Controlgroup
        • +
        • Filter New
        • +
        • Dialogs
        • Fixed toolbars
        • diff --git a/demos/widgets/autocomplete/autocomplete-remote.php b/demos/widgets/autocomplete/autocomplete-remote.php index 21b49278c5e..639b1ec8e7a 100644 --- a/demos/widgets/autocomplete/autocomplete-remote.php +++ b/demos/widgets/autocomplete/autocomplete-remote.php @@ -13,7 +13,7 @@ @@ -60,11 +60,11 @@
          -

          Remote autocomplete

          +

          Remote autocomplete

          -

          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.

          diff --git a/demos/widgets/autocomplete/index.php b/demos/widgets/autocomplete/index.php index 0f06a380e8d..9dbb4299aa1 100644 --- a/demos/widgets/autocomplete/index.php +++ b/demos/widgets/autocomplete/index.php @@ -26,17 +26,17 @@

          Autocomplete API

          -

          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.

          Remote data

          -

          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

          Local data

          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.

          Full width listview (non-inset)

          @@ -96,7 +96,7 @@

          Providing richer search content

          -

          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/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/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 = $( "
          ", { - "class": "ui-listview-filter ui-bar-" + o.filterTheme, - "role": "search" - }).submit( function(/* e */) { - search.blur(); - return false; - }); - search = $( "", { - placeholder: o.filterPlaceholder - }) - .attr( "data-" + $.mobile.ns + "type", "search" ) - .jqmData( "lastval", "" ) - .appendTo( wrapper ) - .textinput(); - - this._on( search, { keyup: "_onKeyUp", change: "_onKeyUp", input: "_onKeyUp" } ); - - $.extend( this, { - _search: search - }); - - if ( o.inset ) { - wrapper.addClass( "ui-listview-filter-inset" ); - } - - wrapper.insertBefore( list ); - } -}); - -})( jQuery ); -//>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude); -}); -//>>excludeEnd("jqmBuildExclude"); From c35285e797fc53366ec442b09daa98c44d302404 Mon Sep 17 00:00:00 2001 From: frequent Date: Tue, 16 Jul 2013 13:23:26 +0200 Subject: [PATCH 13/13] added filter demo pages --- demos/_assets/css/jqm-demos.css | 3 + demos/widgets/filter/index.php | 359 ++++++++++++++++++++++++++++++++ js/widgets/filter.js | 11 +- 3 files changed, 370 insertions(+), 3 deletions(-) create mode 100644 demos/widgets/filter/index.php diff --git a/demos/_assets/css/jqm-demos.css b/demos/_assets/css/jqm-demos.css index 37714cffe29..c59ef8fd1ea 100644 --- a/demos/_assets/css/jqm-demos.css +++ b/demos/_assets/css/jqm-demos.css @@ -123,6 +123,9 @@ body, .jqm-demos { margin: 0 -.3125em; padding: 1px 0; /* to resolve Chrome rendering issue (border-bottom not visible */ } +.jqm-content > .ui-filter { + margin-bottom: 1.1em; +} .jqm-demos .jqm-search > .ui-filter .ui-input-search { margin: 4px .3125em; } 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 + + + + + + + + + + +
          + +
          +

          jQuery Mobile Framework

          + Navigation + Search + +
          + +
          + +

          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. +

          + +
          + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
          RankMovie TitleYearRatingReviews
          1Citizen Kane1941100%74
          2Casablanca194297%64
          3The Godfather197297%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. +

          + +
          + +
          +
          + + +
          +

          This

          +

          are

          +

          Filterable

          +

          Dataset

          +
          + +
          + +
          + + + + + +
          + + diff --git a/js/widgets/filter.js b/js/widgets/filter.js index 23fb6813b23..808c06c89a0 100644 --- a/js/widgets/filter.js +++ b/js/widgets/filter.js @@ -134,8 +134,9 @@ define( [ "jquery", "./forms/textinput" ], function( jQuery ) { } else { this._toggleFilterableItems( filterItems, select, o.filterReveal ); } - - this._addFirstLastClasses( _getFilterableItems, this._getVisibles( _getFilterableItems, false ), false ); + if ( typeof o.selector !== "string" ) { + this._addFirstLastClasses( _getFilterableItems, this._getVisibles( _getFilterableItems, false ), false ); + } }, _toggleFilterableItems: function( filterItems, select, reveal, isVal ) { @@ -235,7 +236,11 @@ define( [ "jquery", "./forms/textinput" ], function( jQuery ) { // "_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. - this._addFirstLastClasses( items, this._getVisibles( items, true ), true ); + + // no classes if multiple datasets are used + if ( typeof o.selector !== "string" ) { + this._addFirstLastClasses( items, this._getVisibles( items, true ), true ); + } }, _setOption: function( key, value ) {