Erste lauffähige Version

This commit is contained in:
Hilmer, Carsten
2016-08-11 16:55:44 +02:00
parent b9bf123abd
commit 4554edbc68
67 changed files with 47204 additions and 73 deletions

View File

@@ -0,0 +1,54 @@
{
"name": "ionic-filter-bar",
"version": "1.1.1",
"description": "A filter directive UI for Ionic apps that animates over the header bar",
"author": "Devin Jett <djett41@gmail.com> (https://github.com/djett41)",
"main": [
"dist/ionic.filter.bar.css",
"dist/ionic.filter.bar.js"
],
"repository": {
"type": "git",
"url": "https://github.com/djett41/ionic-filter-bar.git"
},
"ignore": [
"demo",
"js",
"test",
".gitignore",
"gulpfile.js",
"karma.conf.js",
"LICENSE",
"package.json",
"README.md"
],
"dependencies": {},
"devDependencies": {
"ionic": "^1.0.0-rc.0",
"angular-mocks": "1.4.3"
},
"keywords": [
"mobile",
"html5",
"ionic",
"cordova",
"phonegap",
"search",
"filter",
"angularjs",
"angular"
],
"license": "MIT",
"private": false,
"homepage": "https://github.com/djett41/ionic-filter-bar",
"_release": "1.1.1",
"_resolution": {
"type": "version",
"tag": "v1.1.1",
"commit": "0a0a9f310a911815b45161800b276a9404142825"
},
"_source": "https://github.com/djett41/ionic-filter-bar.git",
"_target": "^1.1.1",
"_originalSource": "ionic-filter-bar",
"_direct": true
}

View File

@@ -0,0 +1,43 @@
{
"name": "ionic-filter-bar",
"version": "1.1.1",
"description": "A filter directive UI for Ionic apps that animates over the header bar",
"author": "Devin Jett <djett41@gmail.com> (https://github.com/djett41)",
"main": [
"dist/ionic.filter.bar.css",
"dist/ionic.filter.bar.js"
],
"repository": {
"type": "git",
"url": "https://github.com/djett41/ionic-filter-bar.git"
},
"ignore": [
"demo",
"js",
"test",
".gitignore",
"gulpfile.js",
"karma.conf.js",
"LICENSE",
"package.json",
"README.md"
],
"dependencies": {},
"devDependencies": {
"ionic": "^1.0.0-rc.0",
"angular-mocks": "1.4.3"
},
"keywords": [
"mobile",
"html5",
"ionic",
"cordova",
"phonegap",
"search",
"filter",
"angularjs",
"angular"
],
"license": "MIT",
"private": false
}

View File

@@ -0,0 +1,224 @@
.filter-bar-backdrop {
-webkit-transition: opacity 150ms ease-in-out;
transition: opacity 150ms ease-in-out;
opacity: 0;
background-color: rgba(0, 0, 0, 0.4);
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%; }
.filter-bar-backdrop.active {
z-index: 10;
opacity: 1; }
.filter-bar {
position: fixed;
width: 100%;
height: 44px;
z-index: 10; }
.filter-bar .filter-bar-wrapper {
z-index: 11;
position: absolute;
top: 0;
right: 0;
width: 100%; }
.filter-bar .filter-bar-wrapper .item-input-inset .icon.placeholder-icon:before {
padding-top: 3px;
font-size: 16px; }
.filter-bar .filter-bar-wrapper .item-input-inset .item-input-wrapper {
background: #fff;
height: 28px; }
.filter-bar .filter-bar-wrapper .item-input-inset .item-input-wrapper .filter-bar-clear {
padding: 0 2px 0 0; }
.filter-bar .filter-bar-wrapper .item-input-inset .item-input-wrapper .filter-bar-clear:before {
color: #aaa;
font-size: 18px;
padding-top: 1px; }
.platform-android .filter-bar .filter-bar-light .item-input-wrapper {
border-bottom: 1px solid #ccc;
background: white; }
.platform-android .filter-bar .filter-bar-light .item-input-wrapper input[type="search"] {
color: #444; }
.platform-android .filter-bar .filter-bar-light .item-input-wrapper input[type="search"]::-moz-placeholder {
color: #aaaaaa; }
.platform-android .filter-bar .filter-bar-light .item-input-wrapper input[type="search"]:-ms-input-placeholder {
color: #aaaaaa; }
.platform-android .filter-bar .filter-bar-light .item-input-wrapper input[type="search"]::-webkit-input-placeholder {
color: #aaaaaa;
text-indent: 0; }
.platform-android .filter-bar .filter-bar-light .item-input-wrapper .filter-bar-clear:before {
color: #444; }
.platform-android .filter-bar .filter-bar-stable .item-input-wrapper {
border-bottom: 1px solid #a2a2a2;
background: #f8f8f8; }
.platform-android .filter-bar .filter-bar-stable .item-input-wrapper input[type="search"] {
color: #444; }
.platform-android .filter-bar .filter-bar-stable .item-input-wrapper input[type="search"]::-moz-placeholder {
color: #aaaaaa; }
.platform-android .filter-bar .filter-bar-stable .item-input-wrapper input[type="search"]:-ms-input-placeholder {
color: #aaaaaa; }
.platform-android .filter-bar .filter-bar-stable .item-input-wrapper input[type="search"]::-webkit-input-placeholder {
color: #aaaaaa;
text-indent: 0; }
.platform-android .filter-bar .filter-bar-stable .item-input-wrapper .filter-bar-clear:before {
color: #444; }
.platform-android .filter-bar .filter-bar-positive .item-input-wrapper {
border-bottom: 1px solid #0c60ee;
background: #387ef5; }
.platform-android .filter-bar .filter-bar-positive .item-input-wrapper input[type="search"] {
color: #fff; }
.platform-android .filter-bar .filter-bar-positive .item-input-wrapper input[type="search"]::-moz-placeholder {
color: white; }
.platform-android .filter-bar .filter-bar-positive .item-input-wrapper input[type="search"]:-ms-input-placeholder {
color: white; }
.platform-android .filter-bar .filter-bar-positive .item-input-wrapper input[type="search"]::-webkit-input-placeholder {
color: white;
text-indent: 0; }
.platform-android .filter-bar .filter-bar-positive .item-input-wrapper .filter-bar-clear:before {
color: #fff; }
.platform-android .filter-bar .filter-bar-calm .item-input-wrapper {
border-bottom: 1px solid #0a9dc7;
background: #11c1f3; }
.platform-android .filter-bar .filter-bar-calm .item-input-wrapper input[type="search"] {
color: #fff; }
.platform-android .filter-bar .filter-bar-calm .item-input-wrapper input[type="search"]::-moz-placeholder {
color: white; }
.platform-android .filter-bar .filter-bar-calm .item-input-wrapper input[type="search"]:-ms-input-placeholder {
color: white; }
.platform-android .filter-bar .filter-bar-calm .item-input-wrapper input[type="search"]::-webkit-input-placeholder {
color: white;
text-indent: 0; }
.platform-android .filter-bar .filter-bar-calm .item-input-wrapper .filter-bar-clear:before {
color: #fff; }
.platform-android .filter-bar .filter-bar-assertive .item-input-wrapper {
border-bottom: 1px solid #e42112;
background: #ef473a; }
.platform-android .filter-bar .filter-bar-assertive .item-input-wrapper input[type="search"] {
color: #fff; }
.platform-android .filter-bar .filter-bar-assertive .item-input-wrapper input[type="search"]::-moz-placeholder {
color: white; }
.platform-android .filter-bar .filter-bar-assertive .item-input-wrapper input[type="search"]:-ms-input-placeholder {
color: white; }
.platform-android .filter-bar .filter-bar-assertive .item-input-wrapper input[type="search"]::-webkit-input-placeholder {
color: white;
text-indent: 0; }
.platform-android .filter-bar .filter-bar-assertive .item-input-wrapper .filter-bar-clear:before {
color: #fff; }
.platform-android .filter-bar .filter-bar-balanced .item-input-wrapper {
border-bottom: 1px solid #28a54c;
background: #33cd5f; }
.platform-android .filter-bar .filter-bar-balanced .item-input-wrapper input[type="search"] {
color: #fff; }
.platform-android .filter-bar .filter-bar-balanced .item-input-wrapper input[type="search"]::-moz-placeholder {
color: white; }
.platform-android .filter-bar .filter-bar-balanced .item-input-wrapper input[type="search"]:-ms-input-placeholder {
color: white; }
.platform-android .filter-bar .filter-bar-balanced .item-input-wrapper input[type="search"]::-webkit-input-placeholder {
color: white;
text-indent: 0; }
.platform-android .filter-bar .filter-bar-balanced .item-input-wrapper .filter-bar-clear:before {
color: #fff; }
.platform-android .filter-bar .filter-bar-energized .item-input-wrapper {
border-bottom: 1px solid #e6b500;
background: #ffc900; }
.platform-android .filter-bar .filter-bar-energized .item-input-wrapper input[type="search"] {
color: #fff; }
.platform-android .filter-bar .filter-bar-energized .item-input-wrapper input[type="search"]::-moz-placeholder {
color: white; }
.platform-android .filter-bar .filter-bar-energized .item-input-wrapper input[type="search"]:-ms-input-placeholder {
color: white; }
.platform-android .filter-bar .filter-bar-energized .item-input-wrapper input[type="search"]::-webkit-input-placeholder {
color: white;
text-indent: 0; }
.platform-android .filter-bar .filter-bar-energized .item-input-wrapper .filter-bar-clear:before {
color: #fff; }
.platform-android .filter-bar .filter-bar-royal .item-input-wrapper {
border-bottom: 1px solid #6b46e5;
background: #886aea; }
.platform-android .filter-bar .filter-bar-royal .item-input-wrapper input[type="search"] {
color: #fff; }
.platform-android .filter-bar .filter-bar-royal .item-input-wrapper input[type="search"]::-moz-placeholder {
color: white; }
.platform-android .filter-bar .filter-bar-royal .item-input-wrapper input[type="search"]:-ms-input-placeholder {
color: white; }
.platform-android .filter-bar .filter-bar-royal .item-input-wrapper input[type="search"]::-webkit-input-placeholder {
color: white;
text-indent: 0; }
.platform-android .filter-bar .filter-bar-royal .item-input-wrapper .filter-bar-clear:before {
color: #fff; }
.platform-android .filter-bar .filter-bar-dark .item-input-wrapper {
border-bottom: 1px solid #000;
background: #444444; }
.platform-android .filter-bar .filter-bar-dark .item-input-wrapper input[type="search"] {
color: #fff; }
.platform-android .filter-bar .filter-bar-dark .item-input-wrapper input[type="search"]::-moz-placeholder {
color: white; }
.platform-android .filter-bar .filter-bar-dark .item-input-wrapper input[type="search"]:-ms-input-placeholder {
color: white; }
.platform-android .filter-bar .filter-bar-dark .item-input-wrapper input[type="search"]::-webkit-input-placeholder {
color: white;
text-indent: 0; }
.platform-android .filter-bar .filter-bar-dark .item-input-wrapper .filter-bar-clear:before {
color: #fff; }
.platform-android .filter-bar .filter-bar-default .item-input-wrapper {
border-bottom: 1px solid #ccc;
background: white; }
.platform-android .filter-bar .filter-bar-default .item-input-wrapper input[type="search"] {
color: #444; }
.platform-android .filter-bar .filter-bar-default .item-input-wrapper input[type="search"]::-moz-placeholder {
color: #aaaaaa; }
.platform-android .filter-bar .filter-bar-default .item-input-wrapper input[type="search"]:-ms-input-placeholder {
color: #aaaaaa; }
.platform-android .filter-bar .filter-bar-default .item-input-wrapper input[type="search"]::-webkit-input-placeholder {
color: #aaaaaa;
text-indent: 0; }
.platform-android .filter-bar .filter-bar-default .item-input-wrapper .filter-bar-clear:before {
color: #444; }
.platform-android .filter-bar-wrapper .item-input-inset {
padding-right: 24px; }
.platform-android .filter-bar-wrapper .item-input-inset .filter-bar-cancel {
padding-left: 0; }
.platform-android .filter-bar-wrapper .item-input-inset .filter-bar-cancel:before {
font-size: 24px; }
.platform-android .filter-bar-wrapper .item-input-inset .item-input-wrapper {
border-radius: 0;
padding-left: 0;
margin-left: 10px; }
.platform-android .filter-bar-wrapper .item-input-inset .item-input-wrapper input[type="search"] {
font-weight: 500; }
.platform-android .filter-bar-wrapper .item-input-inset .item-input-wrapper .filter-bar-clear:before {
font-size: 20px; }
.filter-bar-transition-horizontal {
-webkit-transition: -webkit-transform cubic-bezier(.25, .45, .05, 1) 300ms;
transition: transform cubic-bezier(.25, .45, .05, 1) 300ms;
-webkit-transform: translate3d(100%, 0, 0);
transform: translate3d(100%, 0, 0); }
.filter-bar-transition-vertical {
-webkit-transition: -webkit-transform cubic-bezier(.25, .45, .05, 1) 350ms;
transition: transform cubic-bezier(.25, .45, .05, 1) 350ms;
-webkit-transform: translate3d(0, -100%, 0);
transform: translate3d(0, -100%, 0); }
.filter-bar-transition-fade {
-webkit-transition: opacity 250ms ease-in-out;
transition: opacity 250ms ease-in-out;
opacity: 0; }
.filter-bar-in {
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
opacity: 1; }
.filter-bar-modal .item.item-input {
padding-right: 16px; }
.filter-bar-modal .list-right-editing .item.item-input {
opacity: .5; }
.filter-bar-modal .button.button-icon.ion-ios-checkmark-empty:before {
font-size: 42px; }
.filter-bar-element-hide {
display: none; }

View File

@@ -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);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,179 @@
// Filter Bar
// Variables
//-----------------------------------
$z-index-filter-bar: 11;
// Mixins
//-----------------------------------
@mixin filter-bar-style($filter-bar-bg-color, $filter-bar-active-border-color, $filter-bar-text) {
.item-input-wrapper {
border-bottom: 1px solid $filter-bar-active-border-color;
background: $filter-bar-bg-color;
input[type="search"] {
@include placeholder(lighten($filter-bar-text, 40%));
color: $filter-bar-text;
}
.filter-bar-clear {
&:before {
color: $filter-bar-text;
}
}
}
}
// Styles
//-----------------------------------
.filter-bar-backdrop {
@include transition(opacity 150ms ease-in-out);
opacity: 0;
background-color: rgba(0,0,0,0.4);
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
&.active {
z-index: $z-index-bar-above;
opacity: 1;
}
}
.filter-bar {
position: fixed;
width: 100%;
height: $bar-height;
z-index: $z-index-bar-above;
.filter-bar-wrapper {
z-index: $z-index-filter-bar;
position: absolute;
top: 0;
right:0;
width: 100%;
.item-input-inset {
.icon.placeholder-icon:before {
padding-top: 3px;
font-size: 16px;
}
.item-input-wrapper {
background: $light;
height: 28px;
.filter-bar-clear {
padding: 0 2px 0 0;
&:before {
color: #aaa;
font-size: 18px;
padding-top: 1px;
}
}
}
}
}
}
//android
.platform-android {
.filter-bar {
.filter-bar-light {
@include filter-bar-style($bar-light-bg, $bar-light-active-border, $bar-light-text);
}
.filter-bar-stable {
@include filter-bar-style($bar-stable-bg, $bar-stable-active-border, $bar-stable-text);
}
.filter-bar-positive {
@include filter-bar-style($bar-positive-bg, $bar-positive-active-border, $bar-positive-text);
}
.filter-bar-calm {
@include filter-bar-style($bar-calm-bg, $bar-calm-active-border, $bar-positive-text);
}
.filter-bar-assertive {
@include filter-bar-style($bar-assertive-bg, $bar-assertive-active-border, $bar-assertive-text);
}
.filter-bar-balanced {
@include filter-bar-style($bar-balanced-bg, $bar-balanced-active-border, $bar-balanced-text);
}
.filter-bar-energized {
@include filter-bar-style($bar-energized-bg, $bar-energized-active-border, $bar-energized-text);
}
.filter-bar-royal {
@include filter-bar-style($bar-royal-bg, $bar-royal-active-border, $bar-royal-text);
}
.filter-bar-dark {
@include filter-bar-style($bar-dark-bg, $bar-dark-active-border, $bar-dark-text);
}
.filter-bar-default {
@include filter-bar-style($bar-default-bg, $bar-default-active-border, $bar-default-text)
};
}
.filter-bar-wrapper {
.item-input-inset {
padding-right: $button-padding * 2;
.filter-bar-cancel {
padding-left: 0;
&:before {
font-size: 24px;
}
}
.item-input-wrapper {
border-radius: 0;
padding-left: 0;
margin-left: 10px;
input[type="search"] {
font-weight: 500;
}
.filter-bar-clear:before {
font-size: 20px;
}
}
}
}
}
.filter-bar-transition-horizontal {
@include transition-transform(cubic-bezier(.25, .45, .05, 1) 300ms);
@include translate3d(100%, 0, 0);
}
.filter-bar-transition-vertical {
@include transition-transform(cubic-bezier(.25, .45, .05, 1) 350ms);
@include translate3d(0, -100%, 0);
}
.filter-bar-transition-fade {
@include transition(opacity 250ms ease-in-out) ;
opacity: 0;
}
.filter-bar-in {
@include translate3d(0, 0, 0);
opacity: 1;
}
.filter-bar-modal {
.item {
&.item-input {
padding-right: $item-padding;
}
}
.list-right-editing {
.item.item-input {
opacity: .5;
}
}
//in my opinion the ios checkmark is a little skimp.. make it bigger
.button.button-icon.ion-ios-checkmark-empty:before {
font-size: 42px;
}
}
.filter-bar-hide {
display: none;
}