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