diff --git a/README.md b/README.md index 7e1c63b..aca1295 100644 --- a/README.md +++ b/README.md @@ -239,6 +239,21 @@ A service you can inject in your controller to show the filter bar The filter object used to filter the items array. The default value is $filter('filter'), however you can also pass in a custom filter. + - `{function=}` `search` + + A custom search functionthat returns a filteredItems array. Return value of this function could be promise, which + is useful for backend API filtering with the text entered in the filter bar. + function (filterText){ + var deferred = $q.defer(); + $timeout(function(){ + // imagine items are return value from you api + var result = items.filter(function(item, index){ + return item.text.indexOf(filterText) != -1; + }) + deferred.resolve(result); + }, 500); + } + - `{function=}` `expression` The predicate to be used for selecting items from the `items` array. This is similar to the angular filter diff --git a/demo/app/scripts/app.js b/demo/app/scripts/app.js index 182fd43..5d50a2b 100644 --- a/demo/app/scripts/app.js +++ b/demo/app/scripts/app.js @@ -42,7 +42,7 @@ angular.module('Demo', ['ionic', 'jett.ionic.filter.bar']) }); }) - .controller('MainController', function($scope, $timeout, $ionicFilterBar) { + .controller('MainController', function($scope, $timeout, $q, $ionicFilterBar) { var filterBarInstance; @@ -59,6 +59,16 @@ angular.module('Demo', ['ionic', 'jett.ionic.filter.bar']) $scope.showFilterBar = function () { filterBarInstance = $ionicFilterBar.show({ items: $scope.items, + search: function(filterText){ + var deferred = $q.defer(); + $timeout(function(){ + var result = $scope.items.filter(function(item, index){ + return item.text.indexOf(filterText) != -1; + }) + deferred.resolve(result); + }, 500); + return deferred.promise; + }, update: function (filteredItems, filterText) { $scope.items = filteredItems; if (filterText) { diff --git a/dist/ionic.filter.bar.js b/dist/ionic.filter.bar.js index b7e9e87..2f07dba 100644 --- a/dist/ionic.filter.bar.js +++ b/dist/ionic.filter.bar.js @@ -365,12 +365,13 @@ angular.module('jett.ionic.filter.bar', ['ionic']); '$compile', '$timeout', '$filter', + '$q', '$ionicPlatform', '$ionicFilterBarConfig', '$ionicConfig', '$ionicModal', '$ionicScrollDelegate', - function ($document, $rootScope, $compile, $timeout, $filter, $ionicPlatform, $ionicFilterBarConfig, $ionicConfig, $ionicModal, $ionicScrollDelegate) { + function ($document, $rootScope, $compile, $timeout, $filter, $q, $ionicPlatform, $ionicFilterBarConfig, $ionicConfig, $ionicModal, $ionicScrollDelegate) { var isShown = false; var $body = $document[0].body; var templateConfig = { @@ -435,6 +436,7 @@ angular.module('jett.ionic.filter.bar', ['ionic']); cancelText: 'Cancel', cancelOnStateChange: true, container: $body, + search: null, favoritesTitle: 'Favorite Searches', favoritesAddPlaceholder: 'Add a search term', favoritesEnabled: false, @@ -534,9 +536,11 @@ angular.module('jett.ionic.filter.bar', ['ionic']); var filterExp, filteredItems; // pass back original list if filterText is empty. - // Otherwise filter by expression, supplied properties, or filterText. + // Otherwise perform a search or filter by expression, supplied properties, or filterText. if (!filterText.length) { filteredItems = scope.items; + } else if (angular.isFunction(scope.search)) { + filteredItems = scope.search(filterText); } else { if (scope.expression) { filterExp = angular.bind(this, scope.expression, filterText); @@ -554,11 +558,14 @@ angular.module('jett.ionic.filter.bar', ['ionic']); filteredItems = scope.filter(scope.items, filterExp, scope.comparator); } - - $timeout(function() { - scope.update(filteredItems, filterText); - scope.scrollItemsTop(); - }); + // turn filteredItems into a promise for consistent handling logic + $q.when(filteredItems).then(function(items){ + $timeout(function() { + scope.update(items, filterText); + scope.scrollItemsTop(); + }); + }) + }; // registerBackButtonAction returns a callback to deregister the action @@ -657,6 +664,9 @@ angular.module('jett.ionic.filter.bar', ['ionic']); })(angular, ionic); + + + /* global angular */ (function (angular) { 'use strict'; diff --git a/dist/ionic.filter.bar.min.js b/dist/ionic.filter.bar.min.js index a49c5a5..2d068aa 100644 --- a/dist/ionic.filter.bar.min.js +++ b/dist/ionic.filter.bar.min.js @@ -1 +1 @@ -angular.module("jett.ionic.filter.bar",["ionic"]),function(e,t){"use strict";e.module("jett.ionic.filter.bar").directive("ionFilterBar",["$timeout","$ionicGesture","$ionicPlatform",function(o,r,n){var i;return i=n.is("android")?'
':'
',{restrict:"E",scope:!0,link:function(n,i){var a,l,c,s,d,f=i[0],u=f.querySelector(".filter-bar-clear"),m=f.querySelector(".filter-bar-cancel"),p=f.querySelector(".filter-bar-search"),h=function(){n.cancelFilterBar()};n.config.backdrop&&(c=e.element('
'),i.append(c),s=function(e){e.target==c[0]&&h()},c.bind("click",s),l=r.on("swipe",s,c)),n.favoritesEnabled?n.getClearButtonClass=function(){return n.data.filterText.length?n.config.clear:n.config.favorite}:n.getClearButtonClass=function(){return n.data.filterText.length?n.config.clear:"filter-bar-element-hide"};var b=function(){u.classList.contains(n.config.favorite)?n.showModal():o(function(){n.data.filterText="",ionic.requestAnimationFrame(function(){n.showBackdrop(),n.scrollItemsTop(),n.focusInput()})})},g=function(){n.scrollItemsTop(),n.focusInput()},v=function(e){27==e.which?h():n.data.filterText&&n.data.filterText.length?n.hideBackdrop():n.showBackdrop()};m.addEventListener("click",h),u.addEventListener("touchstart",b),u.addEventListener("mousedown",b),p.addEventListener("touchstart",g),p.addEventListener("mousedown",g),t.addEventListener("keyup",v);var $=function(){n.filterItems(n.data.filterText)};n.$on("$destroy",function(){i.remove(),t.removeEventListener("keyup",v),c&&r.off(l,"swipe",s),d()}),d=n.$watch("data.filterText",function(e,t){var r;a&&o.cancel(a),e!==t&&(r=e.length&&n.debounce?n.delay:0,a=o($,r,!1))})},template:i}}])}(angular,document),function(e){"use strict";e.module("jett.ionic.filter.bar").provider("$ionicFilterBarConfig",function(){function t(e,t){l.platform[e]=t,i.platform[e]={},o(l,l.platform[e]),r(l.platform[e],i.platform[e],"")}function o(t,r){for(var n in t)n!=a&&t.hasOwnProperty(n)&&(e.isObject(t[n])?(e.isDefined(r[n])||(r[n]={}),o(t[n],r[n])):e.isDefined(r[n])||(r[n]=null))}function r(t,o,i){e.forEach(t,function(c,s){e.isObject(t[s])?(o[s]={},r(t[s],o[s],i+"."+s)):o[s]=function(e){if(arguments.length)return t[s]=e,o;if(t[s]==a){var r=n(l.platform,ionic.Platform.platform()+i+"."+s);return r||r===!1?r:n(l.platform,"default"+i+"."+s)}return t[s]}})}function n(t,o){o=o.split(".");for(var r=0;r')(d),$=v.children().eq(0),B=$.find("input")[0],w=v.children().eq(1),k=d.scrollDelegate.getScrollView(),y=!!k,x=y?k.__container:null,I=d.cancelOnStateChange?i.$on("$stateChangeSuccess",function(){d.cancelFilterBar()}):e.noop,T=function(){p||(p=!0,B&&B.focus())},C=function(){p&&(p=!1,B&&B.blur())},F=function(){k.__scrollTop>0&&C()};return d.scrollItemsTop=function(){y&&k.__scrollTop>0&&d.scrollDelegate.scrollTop&&d.scrollDelegate.scrollTop()},d.focusInput=function(){p=!1,T()},d.hideBackdrop=function(){w.length&&f&&(f=!1,w.removeClass("active").css("display","none"))},d.showBackdrop=function(){w.length&&!f&&(f=!0,w.css("display","block").addClass("active"))},d.showModal=function(){d.modal=u.fromTemplate(o,{scope:d}),d.modal.show()},d.filterItems=function(t){var o,r;t.length?(d.expression?o=e.bind(this,d.expression,t):e.isArray(d.filterProperties)?(o={},e.forEach(d.filterProperties,function(e){o[e]=t})):d.filterProperties?(o={},o[d.filterProperties]=t):o=t,r=d.filter(d.items,o,d.comparator)):r=d.items,l(function(){d.update(r,t),d.scrollItemsTop()})},d.$deregisterBackButton=s.registerBackButtonAction(function(){l(d.cancelFilterBar)},300),d.removeFilterBar=function(o){d.removed||(d.removed=!0,t.requestAnimationFrame(function(){$.removeClass("filter-bar-in"),C(),d.hideBackdrop(),l(function(){d.scrollItemsTop(),d.update(d.items),d.$destroy(),v.remove(),d.cancelFilterBar.$scope=d.modal=x=k=$=w=B=null,h=!1,(o||e.noop)()},350)}),l(function(){d.container.classList.remove("filter-bar-open")},400),d.$deregisterBackButton(),I(),x&&x.removeEventListener("scroll",F))},d.showFilterBar=function(o){d.removed||(d.container.appendChild(v[0]),d.container.classList.add("filter-bar-open"),d.scrollItemsTop(),t.requestAnimationFrame(function(){d.removed||l(function(){$.addClass("filter-bar-in"),d.focusInput(),d.showBackdrop(),(o||e.noop)()},20,!1)}),x&&x.addEventListener("scroll",F))},d.cancelFilterBar=function(){d.removeFilterBar(d.cancel)},d.showFilterBar(d.done),d.cancelFilterBar.$scope=d,d.cancelFilterBar}}var h=!1,b=n[0].body,g={theme:d.theme(),transition:d.transition(),back:f.backButton.icon(),clear:d.clear(),favorite:d.favorite(),search:d.search(),backdrop:d.backdrop(),placeholder:d.placeholder(),close:d.close(),done:d.done(),reorder:d.reorder(),remove:d.remove(),add:d.add()};return{show:p}}])}(angular,ionic),function(e){"use strict";e.module("jett.ionic.filter.bar").controller("$ionicFilterBarModalCtrl",["$window","$scope","$timeout","$ionicListDelegate",function(t,o,r,n){var i=o.$parent.favoritesKey;o.displayData={showReorder:!1},o.searches=e.fromJson(t.localStorage.getItem(i))||[],o.newItem={text:""},o.moveItem=function(e,t,n){e.reordered=!0,o.searches.splice(t,1),o.searches.splice(n,0,e),r(function(){delete e.reordered},500)},o.deleteItem=function(e){var t=o.searches.indexOf(e);o.searches.splice(t,1)},o.addItem=function(){o.newItem.text&&(o.searches.push({text:o.newItem.text}),o.newItem.text="")},o.closeModal=function(){t.localStorage.setItem(i,e.toJson(o.searches)),o.$parent.modal.remove()},o.itemClicked=function(e,t){var r=!!t.currentTarget.querySelector(".item-options.invisible");r?(o.closeModal(),o.$parent.hideBackdrop(),o.$parent.data.filterText=e,o.$parent.filterItems(e)):n.$getByHandle("searches-list").closeOptionButtons()}}])}(angular); \ No newline at end of file +angular.module("jett.ionic.filter.bar",["ionic"]),function(e,t){"use strict";e.module("jett.ionic.filter.bar").directive("ionFilterBar",["$timeout","$ionicGesture","$ionicPlatform",function(o,r,n){var i;return i=n.is("android")?'
':'
',{restrict:"E",scope:!0,link:function(n,i){var a,l,c,s,d,f=i[0],u=f.querySelector(".filter-bar-clear"),m=f.querySelector(".filter-bar-cancel"),p=f.querySelector(".filter-bar-search"),h=function(){n.cancelFilterBar()};n.config.backdrop&&(c=e.element('
'),i.append(c),s=function(e){e.target==c[0]&&h()},c.bind("click",s),l=r.on("swipe",s,c)),n.favoritesEnabled?n.getClearButtonClass=function(){return n.data.filterText.length?n.config.clear:n.config.favorite}:n.getClearButtonClass=function(){return n.data.filterText.length?n.config.clear:"filter-bar-element-hide"};var b=function(){u.classList.contains(n.config.favorite)?n.showModal():o(function(){n.data.filterText="",ionic.requestAnimationFrame(function(){n.showBackdrop(),n.scrollItemsTop(),n.focusInput()})})},g=function(){n.scrollItemsTop(),n.focusInput()},v=function(e){27==e.which?h():n.data.filterText&&n.data.filterText.length?n.hideBackdrop():n.showBackdrop()};m.addEventListener("click",h),u.addEventListener("touchstart",b),u.addEventListener("mousedown",b),p.addEventListener("touchstart",g),p.addEventListener("mousedown",g),t.addEventListener("keyup",v);var $=function(){n.filterItems(n.data.filterText)};n.$on("$destroy",function(){i.remove(),t.removeEventListener("keyup",v),c&&r.off(l,"swipe",s),d()}),d=n.$watch("data.filterText",function(e,t){var r;a&&o.cancel(a),e!==t&&(r=e.length&&n.debounce?n.delay:0,a=o($,r,!1))})},template:i}}])}(angular,document),function(e){"use strict";e.module("jett.ionic.filter.bar").provider("$ionicFilterBarConfig",function(){function t(e,t){l.platform[e]=t,i.platform[e]={},o(l,l.platform[e]),r(l.platform[e],i.platform[e],"")}function o(t,r){for(var n in t)n!=a&&t.hasOwnProperty(n)&&(e.isObject(t[n])?(e.isDefined(r[n])||(r[n]={}),o(t[n],r[n])):e.isDefined(r[n])||(r[n]=null))}function r(t,o,i){e.forEach(t,function(c,s){e.isObject(t[s])?(o[s]={},r(t[s],o[s],i+"."+s)):o[s]=function(e){if(arguments.length)return t[s]=e,o;if(t[s]==a){var r=n(l.platform,ionic.Platform.platform()+i+"."+s);return r||r===!1?r:n(l.platform,"default"+i+"."+s)}return t[s]}})}function n(t,o){o=o.split(".");for(var r=0;r')(f),w=$.children().eq(0),B=w.find("input")[0],k=$.children().eq(1),y=f.scrollDelegate.getScrollView(),x=!!y,I=x?y.__container:null,T=f.cancelOnStateChange?i.$on("$stateChangeSuccess",function(){f.cancelFilterBar()}):e.noop,C=function(){h||(h=!0,B&&B.focus())},F=function(){h&&(h=!1,B&&B.blur())},S=function(){y.__scrollTop>0&&F()};return f.scrollItemsTop=function(){x&&y.__scrollTop>0&&f.scrollDelegate.scrollTop&&f.scrollDelegate.scrollTop()},f.focusInput=function(){h=!1,C()},f.hideBackdrop=function(){k.length&&u&&(u=!1,k.removeClass("active").css("display","none"))},f.showBackdrop=function(){k.length&&!u&&(u=!0,k.css("display","block").addClass("active"))},f.showModal=function(){f.modal=m.fromTemplate(o,{scope:f}),f.modal.show()},f.filterItems=function(t){var o,r;t.length?e.isFunction(f.search)?r=f.search(t):(f.expression?o=e.bind(this,f.expression,t):e.isArray(f.filterProperties)?(o={},e.forEach(f.filterProperties,function(e){o[e]=t})):f.filterProperties?(o={},o[f.filterProperties]=t):o=t,r=f.filter(f.items,o,f.comparator)):r=f.items,s.when(r).then(function(e){l(function(){f.update(e,t),f.scrollItemsTop()})})},f.$deregisterBackButton=d.registerBackButtonAction(function(){l(f.cancelFilterBar)},300),f.removeFilterBar=function(o){f.removed||(f.removed=!0,t.requestAnimationFrame(function(){w.removeClass("filter-bar-in"),F(),f.hideBackdrop(),l(function(){f.scrollItemsTop(),f.update(f.items),f.$destroy(),$.remove(),f.cancelFilterBar.$scope=f.modal=I=y=w=k=B=null,b=!1,(o||e.noop)()},350)}),l(function(){f.container.classList.remove("filter-bar-open")},400),f.$deregisterBackButton(),T(),I&&I.removeEventListener("scroll",S))},f.showFilterBar=function(o){f.removed||(f.container.appendChild($[0]),f.container.classList.add("filter-bar-open"),f.scrollItemsTop(),t.requestAnimationFrame(function(){f.removed||l(function(){w.addClass("filter-bar-in"),f.focusInput(),f.showBackdrop(),(o||e.noop)()},20,!1)}),I&&I.addEventListener("scroll",S))},f.cancelFilterBar=function(){f.removeFilterBar(f.cancel)},f.showFilterBar(f.done),f.cancelFilterBar.$scope=f,f.cancelFilterBar}}var b=!1,g=n[0].body,v={theme:f.theme(),transition:f.transition(),back:u.backButton.icon(),clear:f.clear(),favorite:f.favorite(),search:f.search(),backdrop:f.backdrop(),placeholder:f.placeholder(),close:f.close(),done:f.done(),reorder:f.reorder(),remove:f.remove(),add:f.add()};return{show:h}}])}(angular,ionic),function(e){"use strict";e.module("jett.ionic.filter.bar").controller("$ionicFilterBarModalCtrl",["$window","$scope","$timeout","$ionicListDelegate",function(t,o,r,n){var i=o.$parent.favoritesKey;o.displayData={showReorder:!1},o.searches=e.fromJson(t.localStorage.getItem(i))||[],o.newItem={text:""},o.moveItem=function(e,t,n){e.reordered=!0,o.searches.splice(t,1),o.searches.splice(n,0,e),r(function(){delete e.reordered},500)},o.deleteItem=function(e){var t=o.searches.indexOf(e);o.searches.splice(t,1)},o.addItem=function(){o.newItem.text&&(o.searches.push({text:o.newItem.text}),o.newItem.text="")},o.closeModal=function(){t.localStorage.setItem(i,e.toJson(o.searches)),o.$parent.modal.remove()},o.itemClicked=function(e,t){var r=!!t.currentTarget.querySelector(".item-options.invisible");r?(o.closeModal(),o.$parent.hideBackdrop(),o.$parent.data.filterText=e,o.$parent.filterItems(e)):n.$getByHandle("searches-list").closeOptionButtons()}}])}(angular); \ No newline at end of file diff --git a/js/ionic.filter.bar.service.js b/js/ionic.filter.bar.service.js index aca79a1..d6b1f17 100644 --- a/js/ionic.filter.bar.service.js +++ b/js/ionic.filter.bar.service.js @@ -52,12 +52,13 @@ '$compile', '$timeout', '$filter', + '$q', '$ionicPlatform', '$ionicFilterBarConfig', '$ionicConfig', '$ionicModal', '$ionicScrollDelegate', - function ($document, $rootScope, $compile, $timeout, $filter, $ionicPlatform, $ionicFilterBarConfig, $ionicConfig, $ionicModal, $ionicScrollDelegate) { + function ($document, $rootScope, $compile, $timeout, $filter, $q, $ionicPlatform, $ionicFilterBarConfig, $ionicConfig, $ionicModal, $ionicScrollDelegate) { var isShown = false; var $body = $document[0].body; var templateConfig = { @@ -122,6 +123,7 @@ cancelText: 'Cancel', cancelOnStateChange: true, container: $body, + search: null, favoritesTitle: 'Favorite Searches', favoritesAddPlaceholder: 'Add a search term', favoritesEnabled: false, @@ -221,9 +223,11 @@ var filterExp, filteredItems; // pass back original list if filterText is empty. - // Otherwise filter by expression, supplied properties, or filterText. + // Otherwise perform a search or filter by expression, supplied properties, or filterText. if (!filterText.length) { filteredItems = scope.items; + } else if (angular.isFunction(scope.search)) { + filteredItems = scope.search(filterText); } else { if (scope.expression) { filterExp = angular.bind(this, scope.expression, filterText); @@ -241,11 +245,14 @@ filteredItems = scope.filter(scope.items, filterExp, scope.comparator); } - - $timeout(function() { - scope.update(filteredItems, filterText); - scope.scrollItemsTop(); - }); + // turn filteredItems into a promise for consistent handling logic + $q.when(filteredItems).then(function(items){ + $timeout(function() { + scope.update(items, filterText); + scope.scrollItemsTop(); + }); + }) + }; // registerBackButtonAction returns a callback to deregister the action @@ -343,3 +350,6 @@ })(angular, ionic); + + +