|
@@ -0,0 +1,721 @@
|
|
|
+angular.module('jett.ionic.filter.bar', ['ionic']);
|
|
|
+(function (angular, document) {
|
|
|
+ 'use strict';
|
|
|
+
|
|
|
+ angular.module('jett.ionic.filter.bar')
|
|
|
+ .directive('ionFilterBar', [
|
|
|
+ '$timeout',
|
|
|
+ '$ionicGesture',
|
|
|
+ '$ionicPlatform',
|
|
|
+ function ($timeout, $ionicGesture, $ionicPlatform) {
|
|
|
+ var filterBarTemplate;
|
|
|
+
|
|
|
+ //create platform specific filterBar template using filterConfig items
|
|
|
+ if ($ionicPlatform.is('android')) {
|
|
|
+ filterBarTemplate =
|
|
|
+ '<div class="filter-bar-wrapper filter-bar-{{::config.theme}} filter-bar-transition-{{::config.transition}}">' +
|
|
|
+ '<div class="bar bar-header bar-{{::config.theme}} item-input-inset">' +
|
|
|
+ '<button class="filter-bar-cancel button button-icon icon {{::config.back}}"></button>' +
|
|
|
+ '<label class="item-input-wrapper">' +
|
|
|
+ '<input type="search" class="filter-bar-search" ng-model="data.filterText" placeholder="{{::config.placeholder}}" />' +
|
|
|
+ '<button class="filter-bar-clear button button-icon icon" ng-class="getClearButtonClass()"></button>' +
|
|
|
+ '</label>' +
|
|
|
+ '</div>' +
|
|
|
+ '</div>';
|
|
|
+ } else {
|
|
|
+ filterBarTemplate =
|
|
|
+ '<div class="filter-bar-wrapper filter-bar-{{::config.theme}} filter-bar-transition-{{::config.transition}}">' +
|
|
|
+ '<div class="bar bar-header bar-{{::config.theme}} item-input-inset">' +
|
|
|
+ '<label class="item-input-wrapper">' +
|
|
|
+ '<i class="icon {{::config.search}} placeholder-icon"></i>' +
|
|
|
+ '<input type="search" class="filter-bar-search" ng-model="data.filterText" placeholder="{{::config.placeholder}}"/>' +
|
|
|
+ '<button class="filter-bar-clear button button-icon icon" ng-class="getClearButtonClass()"></button>' +
|
|
|
+ '</label>' +
|
|
|
+ '<button class="filter-bar-cancel button button-clear" ng-bind-html="::cancelText"></button>' +
|
|
|
+ '</div>' +
|
|
|
+ '</div>';
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ restrict: 'E',
|
|
|
+ scope: true,
|
|
|
+ link: function ($scope, $element) {
|
|
|
+ var el = $element[0];
|
|
|
+ var clearEl = el.querySelector('.filter-bar-clear');
|
|
|
+ var cancelEl = el.querySelector('.filter-bar-cancel');
|
|
|
+ var inputEl = el.querySelector('.filter-bar-search');
|
|
|
+ var filterTextTimeout;
|
|
|
+ var swipeGesture;
|
|
|
+ var backdrop;
|
|
|
+ var backdropClick;
|
|
|
+ var filterWatch;
|
|
|
+
|
|
|
+ // Action when filter bar is cancelled via backdrop click/swipe or cancel/back buton click.
|
|
|
+ // Invokes cancel function defined in filterBar service
|
|
|
+ var cancelFilterBar = function () {
|
|
|
+ $scope.cancelFilterBar();
|
|
|
+ };
|
|
|
+
|
|
|
+ // If backdrop is enabled, create and append it to filter, then add click/swipe listeners to cancel filter
|
|
|
+ if ($scope.config.backdrop) {
|
|
|
+ backdrop = angular.element('<div class="filter-bar-backdrop"></div>');
|
|
|
+ $element.append(backdrop);
|
|
|
+
|
|
|
+ backdropClick = function(e) {
|
|
|
+ if (e.target == backdrop[0]) {
|
|
|
+ cancelFilterBar();
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ backdrop.bind('click', backdropClick);
|
|
|
+ swipeGesture = $ionicGesture.on('swipe', backdropClick, backdrop);
|
|
|
+ }
|
|
|
+
|
|
|
+ //Sure we could have had 1 function that also checked for favoritesEnabled.. but no need to keep checking a var that wont change
|
|
|
+ if ($scope.favoritesEnabled) {
|
|
|
+ $scope.getClearButtonClass = function () {
|
|
|
+ return $scope.data.filterText.length ? $scope.config.clear : $scope.config.favorite;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ $scope.getClearButtonClass = function () {
|
|
|
+ return $scope.data.filterText.length ? $scope.config.clear : 'filter-bar-element-hide';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // When clear button is clicked, clear filterText, hide clear button, show backdrop, and focus the input
|
|
|
+ var clearClick = function () {
|
|
|
+ if (clearEl.classList.contains($scope.config.favorite)) {
|
|
|
+ $scope.showModal();
|
|
|
+ } else {
|
|
|
+ $timeout(function () {
|
|
|
+ $scope.data.filterText = '';
|
|
|
+ ionic.requestAnimationFrame(function () {
|
|
|
+ $scope.showBackdrop();
|
|
|
+ $scope.scrollItemsTop();
|
|
|
+ $scope.focusInput();
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // Bind touchstart so we can regain focus of input even while scrolling
|
|
|
+ var inputClick = function () {
|
|
|
+ $scope.scrollItemsTop();
|
|
|
+ $scope.focusInput();
|
|
|
+ };
|
|
|
+
|
|
|
+ // When a non escape key is pressed, show/hide backdrop/clear button based on filterText length
|
|
|
+ var keyUp = function(e) {
|
|
|
+ if (e.which == 27) {
|
|
|
+ cancelFilterBar();
|
|
|
+ } else if ($scope.data.filterText && $scope.data.filterText.length) {
|
|
|
+ $scope.hideBackdrop();
|
|
|
+ } else {
|
|
|
+ $scope.showBackdrop();
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ //Event Listeners
|
|
|
+ cancelEl.addEventListener('click', cancelFilterBar);
|
|
|
+ // 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);
|
|
|
+ clearEl.addEventListener('mousedown', clearClick);
|
|
|
+
|
|
|
+ inputEl.addEventListener('touchstart', inputClick);
|
|
|
+ inputEl.addEventListener('mousedown', inputClick);
|
|
|
+
|
|
|
+ document.addEventListener('keyup', keyUp);
|
|
|
+
|
|
|
+ // Calls the services filterItems function with the filterText to filter items
|
|
|
+ var filterItems = function () {
|
|
|
+ $scope.filterItems($scope.data.filterText);
|
|
|
+ };
|
|
|
+
|
|
|
+ // Clean up when scope is destroyed
|
|
|
+ $scope.$on('$destroy', function() {
|
|
|
+ $element.remove();
|
|
|
+ document.removeEventListener('keyup', keyUp);
|
|
|
+ if (backdrop) {
|
|
|
+ $ionicGesture.off(swipeGesture, 'swipe', backdropClick);
|
|
|
+ }
|
|
|
+ filterWatch();
|
|
|
+ });
|
|
|
+
|
|
|
+ // Watch for changes on filterText and call filterItems when filterText has changed.
|
|
|
+ // If debounce is enabled, filter items by the specified or default delay.
|
|
|
+ // Prefer timeout debounce over ng-model-options so if filterText is cleared, initial items show up right away with no delay
|
|
|
+ filterWatch = $scope.$watch('data.filterText', function (newFilterText, oldFilterText) {
|
|
|
+ var delay;
|
|
|
+
|
|
|
+ if (filterTextTimeout) {
|
|
|
+ $timeout.cancel(filterTextTimeout);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (newFilterText !== oldFilterText) {
|
|
|
+ delay = (newFilterText.length && $scope.debounce) ? $scope.delay : 0;
|
|
|
+ filterTextTimeout = $timeout(filterItems, delay, false);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ template: filterBarTemplate
|
|
|
+ };
|
|
|
+ }]);
|
|
|
+
|
|
|
+})(angular, document);
|
|
|
+
|
|
|
+/* global angular */
|
|
|
+/**
|
|
|
+ * This copies the functionality of the ionicConfig provider to allow for platform specific configuration
|
|
|
+ */
|
|
|
+(function (angular) {
|
|
|
+ 'use strict';
|
|
|
+
|
|
|
+ angular.module('jett.ionic.filter.bar')
|
|
|
+ .provider('$ionicFilterBarConfig', function () {
|
|
|
+
|
|
|
+ var provider = this;
|
|
|
+ provider.platform = {};
|
|
|
+ var PLATFORM = 'platform';
|
|
|
+
|
|
|
+ var configProperties = {
|
|
|
+ theme: PLATFORM,
|
|
|
+ clear: PLATFORM,
|
|
|
+ add: PLATFORM,
|
|
|
+ close: PLATFORM,
|
|
|
+ done: PLATFORM,
|
|
|
+ remove: PLATFORM,
|
|
|
+ reorder: PLATFORM,
|
|
|
+ favorite: PLATFORM,
|
|
|
+ search: PLATFORM,
|
|
|
+ backdrop: PLATFORM,
|
|
|
+ transition: PLATFORM,
|
|
|
+ platform: {},
|
|
|
+ placeholder: PLATFORM
|
|
|
+ };
|
|
|
+
|
|
|
+ createConfig(configProperties, provider, '');
|
|
|
+
|
|
|
+ // Default
|
|
|
+ // -------------------------
|
|
|
+ setPlatformConfig('default', {
|
|
|
+ clear: 'ion-ios-close',
|
|
|
+ add: 'ion-ios-plus-outline',
|
|
|
+ close: 'ion-ios-close-empty',
|
|
|
+ done: 'ion-ios-checkmark-empty',
|
|
|
+ remove: 'ion-ios-trash-outline',
|
|
|
+ reorder: 'ion-drag',
|
|
|
+ favorite: 'ion-ios-star',
|
|
|
+ search: 'ion-ios-search-strong',
|
|
|
+ backdrop: true,
|
|
|
+ transition: 'vertical',
|
|
|
+ placeholder: 'Search'
|
|
|
+ });
|
|
|
+
|
|
|
+ // iOS (it is the default already)
|
|
|
+ // -------------------------
|
|
|
+ setPlatformConfig('ios', {});
|
|
|
+
|
|
|
+ // Android
|
|
|
+ // -------------------------
|
|
|
+ setPlatformConfig('android', {
|
|
|
+ clear: 'ion-android-close',
|
|
|
+ close: 'ion-android-close',
|
|
|
+ done: 'ion-android-done',
|
|
|
+ remove: 'ion-android-delete',
|
|
|
+ favorite: 'ion-android-star',
|
|
|
+ search: false,
|
|
|
+ backdrop: false,
|
|
|
+ transition: 'horizontal'
|
|
|
+ });
|
|
|
+
|
|
|
+ provider.setPlatformConfig = setPlatformConfig;
|
|
|
+
|
|
|
+ // private: used to set platform configs
|
|
|
+ function setPlatformConfig(platformName, platformConfigs) {
|
|
|
+ configProperties.platform[platformName] = platformConfigs;
|
|
|
+ provider.platform[platformName] = {};
|
|
|
+
|
|
|
+ addConfig(configProperties, configProperties.platform[platformName]);
|
|
|
+
|
|
|
+ createConfig(configProperties.platform[platformName], provider.platform[platformName], '');
|
|
|
+ }
|
|
|
+
|
|
|
+ // private: used to recursively add new platform configs
|
|
|
+ function addConfig(configObj, platformObj) {
|
|
|
+ for (var n in configObj) {
|
|
|
+ if (n != PLATFORM && configObj.hasOwnProperty(n)) {
|
|
|
+ if (angular.isObject(configObj[n])) {
|
|
|
+ if (!angular.isDefined(platformObj[n])) {
|
|
|
+ platformObj[n] = {};
|
|
|
+ }
|
|
|
+ addConfig(configObj[n], platformObj[n]);
|
|
|
+
|
|
|
+ } else if (!angular.isDefined(platformObj[n])) {
|
|
|
+ platformObj[n] = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // private: create methods for each config to get/set
|
|
|
+ function createConfig(configObj, providerObj, platformPath) {
|
|
|
+ angular.forEach(configObj, function(value, namespace) {
|
|
|
+
|
|
|
+ if (angular.isObject(configObj[namespace])) {
|
|
|
+ // recursively drill down the config object so we can create a method for each one
|
|
|
+ providerObj[namespace] = {};
|
|
|
+ createConfig(configObj[namespace], providerObj[namespace], platformPath + '.' + namespace);
|
|
|
+
|
|
|
+ } else {
|
|
|
+ // create a method for the provider/config methods that will be exposed
|
|
|
+ providerObj[namespace] = function(newValue) {
|
|
|
+ if (arguments.length) {
|
|
|
+ configObj[namespace] = newValue;
|
|
|
+ return providerObj;
|
|
|
+ }
|
|
|
+ if (configObj[namespace] == PLATFORM) {
|
|
|
+ // if the config is set to 'platform', then get this config's platform value
|
|
|
+ var platformConfig = stringObj(configProperties.platform, ionic.Platform.platform() + platformPath + '.' + namespace);
|
|
|
+ if (platformConfig || platformConfig === false) {
|
|
|
+ return platformConfig;
|
|
|
+ }
|
|
|
+ // didnt find a specific platform config, now try the default
|
|
|
+ return stringObj(configProperties.platform, 'default' + platformPath + '.' + namespace);
|
|
|
+ }
|
|
|
+ return configObj[namespace];
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ //splits a string by dot operator and accesses the end var. For example in a.b.c,
|
|
|
+ function stringObj(obj, str) {
|
|
|
+ str = str.split(".");
|
|
|
+ for (var i = 0; i < str.length; i++) {
|
|
|
+ if (obj && angular.isDefined(obj[str[i]])) {
|
|
|
+ obj = obj[str[i]];
|
|
|
+ } else {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return obj;
|
|
|
+ }
|
|
|
+
|
|
|
+ provider.$get = function() {
|
|
|
+ return provider;
|
|
|
+ };
|
|
|
+
|
|
|
+ });
|
|
|
+
|
|
|
+})(angular);
|
|
|
+
|
|
|
+/* global angular,ionic */
|
|
|
+/**
|
|
|
+ * @ngdoc service
|
|
|
+ * @name $ionicFilterBar
|
|
|
+ * @module ionic
|
|
|
+ * @description The Filter Bar is an animated bar that allows a user to search or filter an array of items.
|
|
|
+ */
|
|
|
+(function (angular, ionic) {
|
|
|
+ 'use strict';
|
|
|
+
|
|
|
+ var filterBarModalTemplate =
|
|
|
+ '<ion-modal-view ng-controller="$ionicFilterBarModalCtrl" class="filter-bar-modal">' +
|
|
|
+ '<ion-header-bar class="bar bar-{{::config.theme}} disable-user-behavior">' +
|
|
|
+ '<button class="button button-icon {{::config.close}}" ng-click="closeModal()"></button>' +
|
|
|
+ '<h1 class="title" ng-bind-html="::favoritesTitle"></h1>' +
|
|
|
+ '<button ng-if="searches.length > 1" class="button button-icon" ng-class="displayData.showReorder ? config.done : config.reorder" ng-click="displayData.showReorder = !displayData.showReorder"></button>' +
|
|
|
+ '</ion-header-bar>' +
|
|
|
+ '<ion-content>' +
|
|
|
+ '<ion-list show-reorder="displayData.showReorder" delegate-handle="searches-list">' +
|
|
|
+ '<ion-item ng-repeat="item in searches" class="item-remove-animate" ng-class="{reordered: item.reordered}" ng-click="itemClicked(item.text, $event)">' +
|
|
|
+ '<span ng-bind-html="item.text"></span>' +
|
|
|
+ '<ion-option-button class="button-assertive icon {{::config.remove}}" ng-click="deleteItem(item)"></ion-option-button>' +
|
|
|
+ '<ion-reorder-button class="{{::config.reorder}}" on-reorder="moveItem(item, $fromIndex, $toIndex)"></ion-reorder-button>' +
|
|
|
+ '</ion-item>' +
|
|
|
+ '<div class="item item-input">' +
|
|
|
+ '<input type="text" ng-model="newItem.text" placeholder="{{::favoritesAddPlaceholder}}"/>' +
|
|
|
+ '<button class="button button-icon icon {{::config.add}}" ng-click="addItem(newItem)"></button>' +
|
|
|
+ '</div>' +
|
|
|
+ '</ion-list>' +
|
|
|
+ '</ion-content> ' +
|
|
|
+ '</ion-modal-view>';
|
|
|
+
|
|
|
+ var getNavBarTheme = function ($navBar) {
|
|
|
+ var themes = ['light', 'stable', 'positive', 'calm', 'balanced', 'energized', 'assertive', 'royal', 'dark'];
|
|
|
+ var classList = $navBar && $navBar.classList;
|
|
|
+
|
|
|
+ if (!classList) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (var i = 0; i < themes.length; i++) {
|
|
|
+ if (classList.contains('bar-' + themes[i])) {
|
|
|
+ return themes[i];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ angular.module('jett.ionic.filter.bar')
|
|
|
+ .factory('$ionicFilterBar', [
|
|
|
+ '$document',
|
|
|
+ '$rootScope',
|
|
|
+ '$compile',
|
|
|
+ '$timeout',
|
|
|
+ '$filter',
|
|
|
+ '$ionicPlatform',
|
|
|
+ '$ionicFilterBarConfig',
|
|
|
+ '$ionicConfig',
|
|
|
+ '$ionicModal',
|
|
|
+ '$ionicScrollDelegate',
|
|
|
+ function ($document, $rootScope, $compile, $timeout, $filter, $ionicPlatform, $ionicFilterBarConfig, $ionicConfig, $ionicModal, $ionicScrollDelegate) {
|
|
|
+ var isShown = false;
|
|
|
+ var $body = $document[0].body;
|
|
|
+ var templateConfig = {
|
|
|
+ theme: $ionicFilterBarConfig.theme(),
|
|
|
+ transition: $ionicFilterBarConfig.transition(),
|
|
|
+ back: $ionicConfig.backButton.icon(),
|
|
|
+ clear: $ionicFilterBarConfig.clear(),
|
|
|
+ favorite: $ionicFilterBarConfig.favorite(),
|
|
|
+ search: $ionicFilterBarConfig.search(),
|
|
|
+ backdrop: $ionicFilterBarConfig.backdrop(),
|
|
|
+ placeholder: $ionicFilterBarConfig.placeholder(),
|
|
|
+ close: $ionicFilterBarConfig.close(),
|
|
|
+ done: $ionicFilterBarConfig.done(),
|
|
|
+ reorder: $ionicFilterBarConfig.reorder(),
|
|
|
+ remove: $ionicFilterBarConfig.remove(),
|
|
|
+ add: $ionicFilterBarConfig.add()
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @ngdoc method
|
|
|
+ * @name $ionicFilterBar#show
|
|
|
+ * @description
|
|
|
+ * Load and return a new filter bar.
|
|
|
+ *
|
|
|
+ * A new isolated scope will be created for the filter bar and the new filter bar will be appended to the
|
|
|
+ * body, covering the header bar.
|
|
|
+ *
|
|
|
+ * @returns {function} `hideFilterBar` A function which, when called, hides & cancels the filter bar.
|
|
|
+ */
|
|
|
+ function filterBar (opts) {
|
|
|
+ //if filterBar is already shown return
|
|
|
+ if (isShown) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ isShown = true;
|
|
|
+ opts = opts || {};
|
|
|
+
|
|
|
+ var scope = $rootScope.$new(true);
|
|
|
+ var backdropShown = false;
|
|
|
+ var isKeyboardShown = false;
|
|
|
+
|
|
|
+ //if container option is set, determine the container element by querying for the container class
|
|
|
+ if (opts.container) {
|
|
|
+ opts.container = $body.querySelector(opts.container);
|
|
|
+ }
|
|
|
+
|
|
|
+ //extend scope defaults with supplied options
|
|
|
+ angular.extend(scope, {
|
|
|
+ config: templateConfig,
|
|
|
+ $deregisterBackButton: angular.noop,
|
|
|
+ update: angular.noop,
|
|
|
+ cancel: angular.noop,
|
|
|
+ done: angular.noop,
|
|
|
+ scrollDelegate: $ionicScrollDelegate,
|
|
|
+ filter: $filter('filter'),
|
|
|
+ filterProperties: null,
|
|
|
+ expression: null,
|
|
|
+ comparator: null,
|
|
|
+ debounce: true,
|
|
|
+ delay: 300,
|
|
|
+ cancelText: 'Cancel',
|
|
|
+ cancelOnStateChange: true,
|
|
|
+ container: $body,
|
|
|
+ favoritesTitle: 'Favorite Searches',
|
|
|
+ favoritesAddPlaceholder: 'Add a search term',
|
|
|
+ favoritesEnabled: false,
|
|
|
+ favoritesKey: 'ionic_filter_bar_favorites'
|
|
|
+ }, opts);
|
|
|
+
|
|
|
+ scope.data = {filterText: ''};
|
|
|
+
|
|
|
+ //if no custom theme was configured, get theme of containers bar-header
|
|
|
+ if (!scope.config.theme) {
|
|
|
+ scope.config.theme = getNavBarTheme(scope.container.querySelector('.bar.bar-header'));
|
|
|
+ }
|
|
|
+
|
|
|
+ // Compile the template
|
|
|
+ var element = scope.element = $compile('<ion-filter-bar class="filter-bar"></ion-filter-bar>')(scope);
|
|
|
+
|
|
|
+ // Grab required jQLite elements
|
|
|
+ var filterWrapperEl = element.children().eq(0);
|
|
|
+ var input = filterWrapperEl.find('input')[0];
|
|
|
+ var backdropEl = element.children().eq(1);
|
|
|
+
|
|
|
+ //get scrollView
|
|
|
+ var scrollView = scope.scrollDelegate.getScrollView();
|
|
|
+ var canScroll = !!scrollView;
|
|
|
+
|
|
|
+ //get the scroll container if scrolling is available
|
|
|
+ var $scrollContainer = canScroll ? scrollView.__container : null;
|
|
|
+
|
|
|
+ var stateChangeListenDone = scope.cancelOnStateChange ?
|
|
|
+ $rootScope.$on('$stateChangeSuccess', function () { scope.cancelFilterBar(); }) :
|
|
|
+ angular.noop;
|
|
|
+
|
|
|
+ // Focus the input which will show the keyboard.
|
|
|
+ var showKeyboard = function () {
|
|
|
+ if (!isKeyboardShown) {
|
|
|
+ isKeyboardShown = true;
|
|
|
+ input && input.focus();
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // Blur the input which will hide the keyboard.
|
|
|
+ // Even if we need to bring in ionic.keyboard in the future, blur is preferred so keyboard animates out.
|
|
|
+ var hideKeyboard = function () {
|
|
|
+ if (isKeyboardShown) {
|
|
|
+ isKeyboardShown = false;
|
|
|
+ input && input.blur();
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // When the filtered list is scrolled, we want to hide the keyboard as long as it's not already hidden
|
|
|
+ var handleScroll = function () {
|
|
|
+ if (scrollView.__scrollTop > 0) {
|
|
|
+ hideKeyboard();
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // Scrolls the list of items to the top via the scroll delegate
|
|
|
+ scope.scrollItemsTop = function () {
|
|
|
+ if (canScroll && scrollView.__scrollTop > 0 && scope.scrollDelegate.scrollTop) {
|
|
|
+ scope.scrollDelegate.scrollTop();
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // Set isKeyboardShown to force showing keyboard on search focus.
|
|
|
+ scope.focusInput = function () {
|
|
|
+ isKeyboardShown = false;
|
|
|
+ showKeyboard();
|
|
|
+ };
|
|
|
+
|
|
|
+ // Hide the filterBar backdrop if in the DOM and not already hidden.
|
|
|
+ scope.hideBackdrop = function () {
|
|
|
+ if (backdropEl.length && backdropShown) {
|
|
|
+ backdropShown = false;
|
|
|
+ backdropEl.removeClass('active').css('display', 'none');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // Show the filterBar backdrop if in the DOM and not already shown.
|
|
|
+ scope.showBackdrop = function () {
|
|
|
+ if (backdropEl.length && !backdropShown) {
|
|
|
+ backdropShown = true;
|
|
|
+ backdropEl.css('display', 'block').addClass('active');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ scope.showModal = function () {
|
|
|
+ scope.modal = $ionicModal.fromTemplate(filterBarModalTemplate, {
|
|
|
+ scope: scope
|
|
|
+ });
|
|
|
+ scope.modal.show();
|
|
|
+ };
|
|
|
+
|
|
|
+ // Filters the supplied list of items via the supplied filterText.
|
|
|
+ // How items are filtered depends on the supplied filter object, and expression
|
|
|
+ // Filtered items will be sent to update
|
|
|
+ scope.filterItems = function(filterText) {
|
|
|
+ var filterExp, filteredItems;
|
|
|
+
|
|
|
+ // pass back original list if filterText is empty.
|
|
|
+ // Otherwise filter by expression, supplied properties, or filterText.
|
|
|
+ if (!filterText.length) {
|
|
|
+ filteredItems = scope.items;
|
|
|
+ } else {
|
|
|
+ if (scope.expression) {
|
|
|
+ filterExp = angular.bind(this, scope.expression, filterText);
|
|
|
+ } else if (angular.isArray(scope.filterProperties)) {
|
|
|
+ filterExp = {};
|
|
|
+ angular.forEach(scope.filterProperties, function (property) {
|
|
|
+ filterExp[property] = filterText;
|
|
|
+ });
|
|
|
+ } else if (scope.filterProperties) {
|
|
|
+ filterExp = {};
|
|
|
+ filterExp[scope.filterProperties] = filterText;
|
|
|
+ } else {
|
|
|
+ filterExp = filterText;
|
|
|
+ }
|
|
|
+
|
|
|
+ filteredItems = scope.filter(scope.items, filterExp, scope.comparator);
|
|
|
+ }
|
|
|
+
|
|
|
+ $timeout(function() {
|
|
|
+ scope.update(filteredItems, filterText);
|
|
|
+ scope.scrollItemsTop();
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ // registerBackButtonAction returns a callback to deregister the action
|
|
|
+ scope.$deregisterBackButton = $ionicPlatform.registerBackButtonAction(
|
|
|
+ function() {
|
|
|
+ $timeout(scope.cancelFilterBar);
|
|
|
+ }, 300
|
|
|
+ );
|
|
|
+
|
|
|
+ // Removes the filterBar from the body and cleans up vars/events. Once the backdrop is hidden we can invoke done
|
|
|
+ scope.removeFilterBar = function(done) {
|
|
|
+ if (scope.removed) return;
|
|
|
+
|
|
|
+ scope.removed = true;
|
|
|
+
|
|
|
+ //animate the filterBar out, hide keyboard and backdrop
|
|
|
+ ionic.requestAnimationFrame(function () {
|
|
|
+ filterWrapperEl.removeClass('filter-bar-in');
|
|
|
+ hideKeyboard();
|
|
|
+ scope.hideBackdrop();
|
|
|
+
|
|
|
+ //Wait before cleaning up so element isn't removed before filter bar animates out
|
|
|
+ $timeout(function () {
|
|
|
+ scope.scrollItemsTop();
|
|
|
+ scope.update(scope.items);
|
|
|
+
|
|
|
+ scope.$destroy();
|
|
|
+ element.remove();
|
|
|
+ scope.cancelFilterBar.$scope = scope.modal = $scrollContainer = scrollView = filterWrapperEl = backdropEl = input = null;
|
|
|
+ isShown = false;
|
|
|
+ (done || angular.noop)();
|
|
|
+ }, 350);
|
|
|
+ });
|
|
|
+
|
|
|
+ $timeout(function () {
|
|
|
+ // wait to remove this due to a 300ms delay native
|
|
|
+ // click which would trigging whatever was underneath this
|
|
|
+ scope.container.classList.remove('filter-bar-open');
|
|
|
+ }, 400);
|
|
|
+
|
|
|
+ scope.$deregisterBackButton();
|
|
|
+ stateChangeListenDone();
|
|
|
+
|
|
|
+ //unbind scroll event
|
|
|
+ if ($scrollContainer) {
|
|
|
+ $scrollContainer.removeEventListener('scroll', handleScroll);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // Appends the filterBar to the body. Once the backdrop is hidden we can invoke done
|
|
|
+ scope.showFilterBar = function(done) {
|
|
|
+ if (scope.removed) return;
|
|
|
+
|
|
|
+ scope.container.appendChild(element[0]);
|
|
|
+ scope.container.classList.add('filter-bar-open');
|
|
|
+
|
|
|
+ //scroll items to the top before starting the animation
|
|
|
+ scope.scrollItemsTop();
|
|
|
+
|
|
|
+ //start filterBar animation, show backrop and focus the input
|
|
|
+ ionic.requestAnimationFrame(function () {
|
|
|
+ if (scope.removed) return;
|
|
|
+
|
|
|
+ $timeout(function () {
|
|
|
+ filterWrapperEl.addClass('filter-bar-in');
|
|
|
+ scope.focusInput();
|
|
|
+ scope.showBackdrop();
|
|
|
+ (done || angular.noop)();
|
|
|
+ }, 20, false);
|
|
|
+ });
|
|
|
+
|
|
|
+ if ($scrollContainer) {
|
|
|
+ $scrollContainer.addEventListener('scroll', handleScroll);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // called when the user presses the backdrop, cancel/back button, changes state
|
|
|
+ scope.cancelFilterBar = function() {
|
|
|
+ // after the animation is out, call the cancel callback
|
|
|
+ scope.removeFilterBar(scope.cancel);
|
|
|
+ };
|
|
|
+
|
|
|
+ scope.showFilterBar(scope.done);
|
|
|
+
|
|
|
+ // Expose the scope on $ionFilterBar's return value for the sake of testing it.
|
|
|
+ scope.cancelFilterBar.$scope = scope;
|
|
|
+
|
|
|
+ return scope.cancelFilterBar;
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ show: filterBar
|
|
|
+ };
|
|
|
+ }]);
|
|
|
+
|
|
|
+
|
|
|
+})(angular, ionic);
|
|
|
+
|
|
|
+/* global angular */
|
|
|
+(function (angular) {
|
|
|
+ 'use strict';
|
|
|
+
|
|
|
+ angular.module('jett.ionic.filter.bar')
|
|
|
+ .controller('$ionicFilterBarModalCtrl', [
|
|
|
+ '$window',
|
|
|
+ '$scope',
|
|
|
+ '$timeout',
|
|
|
+ '$ionicListDelegate',
|
|
|
+ function ($window, $scope, $timeout, $ionicListDelegate) {
|
|
|
+ var searchesKey = $scope.$parent.favoritesKey;
|
|
|
+
|
|
|
+ $scope.displayData = {showReorder: false};
|
|
|
+ $scope.searches = angular.fromJson($window.localStorage.getItem(searchesKey)) || [];
|
|
|
+ $scope.newItem = {text: ''};
|
|
|
+
|
|
|
+ $scope.moveItem = function(item, fromIndex, toIndex) {
|
|
|
+ item.reordered = true;
|
|
|
+ $scope.searches.splice(fromIndex, 1);
|
|
|
+ $scope.searches.splice(toIndex, 0, item);
|
|
|
+
|
|
|
+ $timeout(function () {
|
|
|
+ delete item.reordered;
|
|
|
+ }, 500);
|
|
|
+ };
|
|
|
+
|
|
|
+ $scope.deleteItem = function(item) {
|
|
|
+ var index = $scope.searches.indexOf(item);
|
|
|
+ $scope.searches.splice(index, 1);
|
|
|
+ };
|
|
|
+
|
|
|
+ $scope.addItem = function () {
|
|
|
+ if ($scope.newItem.text) {
|
|
|
+ $scope.searches.push({
|
|
|
+ text: $scope.newItem.text
|
|
|
+ });
|
|
|
+ $scope.newItem.text = '';
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ $scope.closeModal = function () {
|
|
|
+ $window.localStorage.setItem(searchesKey, angular.toJson($scope.searches));
|
|
|
+ $scope.$parent.modal.remove();
|
|
|
+ };
|
|
|
+
|
|
|
+ $scope.itemClicked = function (filterText, $event) {
|
|
|
+ var isOptionButtonsClosed = !!$event.currentTarget.querySelector('.item-options.invisible');
|
|
|
+
|
|
|
+ if (isOptionButtonsClosed) {
|
|
|
+ $scope.closeModal();
|
|
|
+ $scope.$parent.hideBackdrop();
|
|
|
+ $scope.$parent.data.filterText = filterText;
|
|
|
+ $scope.$parent.filterItems(filterText);
|
|
|
+ } else {
|
|
|
+ $ionicListDelegate.$getByHandle('searches-list').closeOptionButtons();
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ }]);
|
|
|
+
|
|
|
+})(angular);
|