diff --git a/README.md b/README.md index 259881b..29789f4 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,29 @@ Run the following ommands: bower install gulp +## Remote Searching +The default behaviour supports searching locally with a given list of items. In case you need to implement your own searching which connects to a remote server of retrieving data, you need to disable `hideBackdropOnTyping`. Also depending on your implementation, you might different behaviours for events such as user clicks on cancel button or taps on backdrop. The following example gives you the idea on how this can be implemented. + +```javascript + var filterBarInstanceTerminator = $ionicFilterBar.show({ + update: function (filteredItems, filterText) { + console.log(filterText); + }, + cancelText: 'Search', + backdrop: true, + hideBackdropOnTyping: false, + cancelClick: function (items, text) { + console.log('cancelClick:' + text); + filterBarInstanceTerminator(); + }, + backdropClick: function (items, text) { + console.log('backdropClick:' + text); + filterBarInstanceTerminator(); + } + }); + +``` + ## Setup #### Install @@ -225,6 +248,36 @@ A service you can inject in your controller to show the filter bar Called after the filterBar is removed. This can happen when the cancel button is pressed, the backdrop is tapped or swiped, or the back button is pressed. + - `{function=}` `cancelClick` + Called when user clicks on cancel button. When you use this option, backdrop will not disappear + automatically. Instead you have to close it manually by calling the filter bar instance. + ```javascript + var filterBarInstanceTerminator = $ionicFilterBar.show({ + update: function (filteredItems, filterText) { + console.log(filterText); + }, + cancelText: 'Search', + backdrop: true, + hideBackdropOnTyping: false, + cancelClick: function (items, text) { + console.log('cancelClick:' + text); + filterBarInstanceTerminator(); + }, + backdropClick: function (items, text) { + console.log('backdropClick:' + text); + filterBarInstanceTerminator(); + } + }); + + ``` + + - `{function=}` `backdropClick` + Called when user taps on backdrop. When you use this option, backdrop will not disappear + automatically. Instead you have to close it manually by calling the filter bar instance. + You can have a look at the example given for `cancelClick` option + + - `{boolean=}` `hideBackdropOnTyping` + The default value is true. When user types into the search box, backdrop is hidden automatically. - `{function=}` `done` diff --git a/bower.json b/bower.json index c28f778..02dfe26 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "ionic-filter-bar", - "version": "1.1.1", + "version": "1.2.1", "description": "A filter directive UI for Ionic apps that animates over the header bar", "author": "Devin Jett (https://github.com/djett41)", "main": [ @@ -39,5 +39,8 @@ "angular" ], "license": "MIT", - "private": false + "private": false, + "resolutions": { + "angular": "1.5.3" + } } diff --git a/dist/ionic.filter.bar.js b/dist/ionic.filter.bar.js index b7e9e87..49b0c8a 100644 --- a/dist/ionic.filter.bar.js +++ b/dist/ionic.filter.bar.js @@ -63,7 +63,12 @@ angular.module('jett.ionic.filter.bar', ['ionic']); backdropClick = function(e) { if (e.target == backdrop[0]) { - cancelFilterBar(); + if ($scope.backdropClick !== angular.noop) { + $scope.backdropClick($scope.data.filterItems, + $scope.data.filterText); + } else { + cancelFilterBar(); + } } }; @@ -108,15 +113,26 @@ angular.module('jett.ionic.filter.bar', ['ionic']); var keyUp = function(e) { if (e.which == 27) { cancelFilterBar(); - } else if ($scope.data.filterText && $scope.data.filterText.length) { + } else if ($scope.data.filterText + && $scope.data.filterText.length + && $scope.hideBackdropOnTyping) { $scope.hideBackdrop(); } else { $scope.showBackdrop(); } }; + var cancelElClickEvent = function() { + if ($scope.cancelClick !== angular.noop) { + $scope.cancelClick($scope.data.filterItems, + $scope.data.filterText); + } else { + cancelFilterBar(); + } + }; + //Event Listeners - cancelEl.addEventListener('click', cancelFilterBar); + cancelEl.addEventListener('click', cancelElClickEvent); // Since we are wrapping with label, need to bind touchstart rather than click. // Even if we use div instead of label need to bind touchstart. Click isn't allowing input to regain focus quickly clearEl.addEventListener('touchstart', clearClick); @@ -424,6 +440,9 @@ angular.module('jett.ionic.filter.bar', ['ionic']); $deregisterBackButton: angular.noop, update: angular.noop, cancel: angular.noop, + cancelClick: angular.noop, + backdropClick: angular.noop, + hideBackdropOnTyping: true, done: angular.noop, scrollDelegate: $ionicScrollDelegate, filter: $filter('filter'), diff --git a/dist/ionic.filter.bar.min.js b/dist/ionic.filter.bar.min.js index a49c5a5..2f43fe4 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,c,l,s,d,f=i[0],u=f.querySelector(".filter-bar-clear"),p=f.querySelector(".filter-bar-cancel"),m=f.querySelector(".filter-bar-search"),h=function(){n.cancelFilterBar()};n.config.backdrop&&(l=e.element('
'),i.append(l),s=function(t){t.target==l[0]&&(n.backdropClick!==e.noop?n.backdropClick(n.data.filterItems,n.data.filterText):h())},l.bind("click",s),c=r.on("swipe",s,l)),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.hideBackdropOnTyping?n.hideBackdrop():n.showBackdrop()},k=function(){n.cancelClick!==e.noop?n.cancelClick(n.data.filterItems,n.data.filterText):h()};p.addEventListener("click",k),u.addEventListener("touchstart",b),u.addEventListener("mousedown",b),m.addEventListener("touchstart",g),m.addEventListener("mousedown",g),t.addEventListener("keyup",v);var $=function(){n.filterItems(n.data.filterText)};n.$on("$destroy",function(){i.remove(),t.removeEventListener("keyup",v),l&&r.off(c,"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){c.platform[e]=t,i.platform[e]={},o(c,c.platform[e]),r(c.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(l,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(c.platform,ionic.Platform.platform()+i+"."+s);return r||r===!1?r:n(c.platform,"default"+i+"."+s)}return t[s]}})}function n(t,o){o=o.split(".");for(var r=0;r')(d),k=v.children().eq(0),$=k.find("input")[0],B=v.children().eq(1),w=d.scrollDelegate.getScrollView(),y=!!w,x=y?w.__container:null,C=d.cancelOnStateChange?i.$on("$stateChangeSuccess",function(){d.cancelFilterBar()}):e.noop,I=function(){m||(m=!0,$&&$.focus())},T=function(){m&&(m=!1,$&&$.blur())},F=function(){w.__scrollTop>0&&T()};return d.scrollItemsTop=function(){y&&w.__scrollTop>0&&d.scrollDelegate.scrollTop&&d.scrollDelegate.scrollTop()},d.focusInput=function(){m=!1,I()},d.hideBackdrop=function(){B.length&&f&&(f=!1,B.removeClass("active").css("display","none"))},d.showBackdrop=function(){B.length&&!f&&(f=!0,B.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,c(function(){d.update(r,t),d.scrollItemsTop()})},d.$deregisterBackButton=s.registerBackButtonAction(function(){c(d.cancelFilterBar)},300),d.removeFilterBar=function(o){d.removed||(d.removed=!0,t.requestAnimationFrame(function(){k.removeClass("filter-bar-in"),T(),d.hideBackdrop(),c(function(){d.scrollItemsTop(),d.update(d.items),d.$destroy(),v.remove(),d.cancelFilterBar.$scope=d.modal=x=w=k=B=$=null,h=!1,(o||e.noop)()},350)}),c(function(){d.container.classList.remove("filter-bar-open")},400),d.$deregisterBackButton(),C(),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||c(function(){k.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:m}}])}(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.directive.js b/js/ionic.filter.bar.directive.js index cce8596..7466687 100644 --- a/js/ionic.filter.bar.directive.js +++ b/js/ionic.filter.bar.directive.js @@ -62,7 +62,12 @@ backdropClick = function(e) { if (e.target == backdrop[0]) { - cancelFilterBar(); + if ($scope.backdropClick !== angular.noop) { + $scope.backdropClick($scope.data.filterItems, + $scope.data.filterText); + } else { + cancelFilterBar(); + } } }; @@ -107,15 +112,26 @@ var keyUp = function(e) { if (e.which == 27) { cancelFilterBar(); - } else if ($scope.data.filterText && $scope.data.filterText.length) { + } else if ($scope.data.filterText + && $scope.data.filterText.length + && $scope.hideBackdropOnTyping) { $scope.hideBackdrop(); } else { $scope.showBackdrop(); } }; + var cancelElClickEvent = function() { + if ($scope.cancelClick !== angular.noop) { + $scope.cancelClick($scope.data.filterItems, + $scope.data.filterText); + } else { + cancelFilterBar(); + } + }; + //Event Listeners - cancelEl.addEventListener('click', cancelFilterBar); + cancelEl.addEventListener('click', cancelElClickEvent); // Since we are wrapping with label, need to bind touchstart rather than click. // Even if we use div instead of label need to bind touchstart. Click isn't allowing input to regain focus quickly clearEl.addEventListener('touchstart', clearClick); diff --git a/js/ionic.filter.bar.service.js b/js/ionic.filter.bar.service.js index aca79a1..5b019b1 100644 --- a/js/ionic.filter.bar.service.js +++ b/js/ionic.filter.bar.service.js @@ -111,6 +111,9 @@ $deregisterBackButton: angular.noop, update: angular.noop, cancel: angular.noop, + cancelClick: angular.noop, + backdropClick: angular.noop, + hideBackdropOnTyping: true, done: angular.noop, scrollDelegate: $ionicScrollDelegate, filter: $filter('filter'), diff --git a/karma.conf.js b/karma.conf.js index f480ca7..42579f7 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -33,5 +33,5 @@ module.exports = { // - Safari (only Mac; has to be installed with `npm install karma-safari-launcher`) // - PhantomJS // - IE (only Windows; has to be installed with `npm install karma-ie-launcher`) - browsers: ['PhantomJS'] + browsers: ['Chrome'] }; diff --git a/package.json b/package.json index 431d8ce..8227353 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ionic-filter-bar", - "version": "1.1.1", + "version": "1.2.1", "description": "A filter directive UI for Ionic apps that animates over the header bar", "author": "Devin Jett (https://github.com/djett41)", "main": "./dist/ionic-filter-bar.js", @@ -13,19 +13,19 @@ "url": "https://github.com/djett41/ionic-filter-bar.git" }, "devDependencies": { + "bower": "^1.3.3", "gulp": "^3.5.6", - "gulp-sass": "^1.3.3", "gulp-concat": "^2.2.0", "gulp-minify-css": "^0.3.0", "gulp-rename": "^1.2.0", - "bower": "^1.3.3", + "gulp-sass": "^2.3.2", "gulp-uglify": "^0.2.1", "gulp-util": "^2.2.14", - "shelljs": "^0.3.0", "karma": "^0.12.23", "karma-chrome-launcher": "^0.1.4", "karma-jasmine": "~0.1.5", - "karma-phantomjs-launcher": "~0.1.2" + "karma-phantomjs-launcher": "~0.1.2", + "shelljs": "^0.3.0" }, "scripts": { "test": "gulp test" diff --git a/test/unit/ionic.filter.bar.service.unit.js b/test/unit/ionic.filter.bar.service.unit.js index e8d7337..92796b1 100644 --- a/test/unit/ionic.filter.bar.service.unit.js +++ b/test/unit/ionic.filter.bar.service.unit.js @@ -47,6 +47,9 @@ describe('Ionic FilterBar Service', function() { expect(scope.update).toEqual(angular.noop); expect(scope.cancel).toEqual(angular.noop); + expect(scope.cancelClick).toEqual(angular.noop); + expect(scope.hideBackdropOnTyping).toEqual(true); + expect(scope.backdropClick).toEqual(angular.noop); expect(scope.done).toEqual(angular.noop); expect(scope.filterProperties).toBeNull(); expect(scope.debounce).toBe(true); @@ -182,11 +185,16 @@ describe('Ionic FilterBar Service', function() { var update = function () {}; var cancel = function () {}; var done = function () {}; + var cancelClick = function() {}; + var backdropClick = function() {}; var scope = setup({ update: update, + cancelClick: cancelClick, + backdropClick: backdropClick, cancel: cancel, done: done, + hideBackdropOnTyping: false, filterProperties: ['propA', 'propB'], debounce: false, delay: 0, @@ -213,6 +221,9 @@ describe('Ionic FilterBar Service', function() { expect(scope.update).toEqual(update); expect(scope.cancel).toEqual(cancel); + expect(scope.cancelClick).toEqual(cancelClick); + expect(scope.hideBackdropOnTyping).toEqual(false); + expect(scope.backdropClick).toEqual(backdropClick); expect(scope.done).toEqual(done); expect(scope.filterProperties).toEqual(['propA', 'propB']); expect(scope.debounce).toBe(false);