├── .gitignore ├── README.md ├── app ├── app.config.js ├── app.controller.js ├── app.directive.js ├── assets │ ├── fonts │ │ ├── icons_mobiscroll.eot │ │ ├── icons_mobiscroll.svg │ │ ├── icons_mobiscroll.ttf │ │ ├── icons_mobiscroll.woff │ │ ├── ionicons.eot │ │ ├── ionicons.svg │ │ ├── ionicons.ttf │ │ └── ionicons.woff │ ├── lib │ │ ├── calendar-pk │ │ │ └── release │ │ │ │ ├── css │ │ │ │ ├── calendar_pk.css │ │ │ │ └── calendar_pk.min.css │ │ │ │ └── js │ │ │ │ ├── calendar_pk.js │ │ │ │ └── calendar_pk.min.js │ │ ├── ionic-datepicker │ │ │ └── release │ │ │ │ └── ionic-datepicker.bundle.min.js │ │ ├── ionic-filter-bar │ │ │ └── release │ │ │ │ ├── ionic.filter.bar.css │ │ │ │ ├── ionic.filter.bar.js │ │ │ │ ├── ionic.filter.bar.min.css │ │ │ │ └── ionic.filter.bar.min.js │ │ ├── ionic │ │ │ └── release │ │ │ │ └── js │ │ │ │ ├── ionic.bundle.js │ │ │ │ ├── ionic.bundle.min.js │ │ │ │ └── ionic.min.js │ │ └── pdfjs-dist │ │ │ └── web │ │ │ ├── viewer.css │ │ │ ├── viewer.html │ │ │ └── viewer.js │ ├── scss │ │ ├── _index.scss │ │ ├── _mainInterface.scss │ │ └── main.scss │ └── styles │ │ └── ionic.min.css ├── config.route.js ├── index.js └── modules │ └── business │ ├── index.js │ └── main │ ├── main.controller.js │ └── main.html ├── build ├── configDevServer.js ├── webpack.base.config.js ├── webpack.dev.config.js └── webpack.prod.config.js ├── docs ├── angular-m-part.md └── webpack-part.md ├── index.html └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | .idea/ 3 | node_modules/ 4 | dist 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 基于webpack构建的angularjs1.x + ionic 1.x 工程 2 | 3 | 这是一个基于webpack的angularjs 1.x和ionic 1.x的工程脚手架。 4 | 5 | 给需要重构angular老工程的朋友的一个参考 6 | (是思路参考,如果是需要新建angularjs工程的朋友请不要使用,但是可以参考来学习一下webpack的基本的配置方法。) 7 | 8 | >初始化工程 9 | ``` 10 | npm install 11 | ``` 12 | 这条命令会自动安装所有必要的依赖,这里用到的angularjs版本是1.4.3 13 | ionic的版本是1.2.4(不过由于用``npm``安装``ionic``会提示找不到``ionic``的错误,所以我把ionic附在了`/app/assets/lib`文件夹里面,直接引用) 14 | >运行工程/开发模式打包 15 | ``` 16 | npm run dev 17 | ``` 18 | 这条命令会打包并生成`dev-server`,然后可以在`localhost:8089`端口访问到这个项目的开发环境(端口配置和接口代理可以在`build/configServer.js`里配置) 19 | >生产模式打包 20 | ``` 21 | npm run build 22 | ``` 23 | 这条命令会打包成生产环境的包供部署。 24 | 25 | 注意: 目前此工程中使用的webpack-dev-server由于版本过低,没有限制开发环境中的websocke访问的客户源主机,所以可能会造成开发过程中的开发代码泄露的问题。曾尝试过将此工程中的webpack-devServer升级,发现并不兼容。所以不针对这个问题进行修复。如不介意的话可以使用。 26 | 27 | 更多信息可以看本脚手架里其他两篇文章: 28 | [基于webpack构建的angular 1.x工程(webpack篇)](./docs/webpack-part.md) 29 | 30 | [基于webpack构建的angular 1.x工程(angular篇)](./docs/angular-m-part.md) -------------------------------------------------------------------------------- /app/app.config.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = angular.module("app") 4 | .config(['CacheFactoryProvider', '$cookiesProvider', '$ionicConfigProvider', '$ionicFilterBarConfigProvider', 'ionicDatePickerProvider', '$httpProvider', function (CacheFactoryProvider, $cookiesProvider, $ionicConfigProvider, $ionicFilterBarConfigProvider, ionicDatePickerProvider, $httpProvider) { 5 | angular.extend($cookiesProvider.defaults, { 6 | path: "/" 7 | }); 8 | angular.extend(CacheFactoryProvider.defaults, { 9 | maxAge: 1 10 | }); //缓存10s 11 | 12 | $ionicConfigProvider.views.transition('ios'); 13 | $ionicConfigProvider.views.swipeBackEnabled(true); 14 | $ionicConfigProvider.scrolling.jsScrolling(false); 15 | $ionicConfigProvider.platform.android.views.maxCache(0); 16 | $ionicConfigProvider.platform.android.tabs.style('standard'); 17 | $ionicConfigProvider.platform.android.tabs.position('standard'); 18 | $ionicConfigProvider.navBar.alignTitle('center').positionPrimaryButtons('left'); 19 | 20 | 21 | $ionicFilterBarConfigProvider.theme('positive'); 22 | $ionicFilterBarConfigProvider.clear('ion-close'); 23 | $ionicFilterBarConfigProvider.search('ion-search'); 24 | $ionicFilterBarConfigProvider.backdrop(false); 25 | $ionicFilterBarConfigProvider.transition('vertical'); 26 | $ionicFilterBarConfigProvider.placeholder('Filter'); 27 | $ionicConfigProvider.platform.android.navBar.alignTitle('left'); 28 | $ionicConfigProvider.platform.android.backButton.previousTitleText('').icon('ion-android-arrow-back'); 29 | $ionicConfigProvider.platform.android.views.transition('android'); 30 | $ionicConfigProvider.views.maxCache(0); 31 | 32 | 33 | var datePickerObj = { 34 | inputDate: new Date(), 35 | setLabel: '确定', 36 | todayLabel: '今天', 37 | closeLabel: '关闭', 38 | mondayFirst: false, 39 | weeksList: ["日", "一", "二", "三", "四", "五", "六"], 40 | monthsList: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"], 41 | templateType: 'popup', 42 | from: new Date(1910, 1, 1), 43 | to: new Date(2050, 12, 31), 44 | showTodayButton: true, 45 | dateFormat: 'yyyy-MM-dd', 46 | closeOnSelect: false, 47 | disableWeekdays: [] 48 | }; 49 | ionicDatePickerProvider.configDatePicker(datePickerObj); 50 | 51 | /*http post 方法的传参格式更改--start--csk20161005*/ 52 | // Use x-www-form-urlencoded Content-Type 53 | $httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8'; 54 | $httpProvider.defaults.cache = false; 55 | //$httpProvider.defaults.headers['Range'] = 'bytes=0-199999'; 56 | /** 57 | * The workhorse; converts an object to x-www-form-urlencoded serialization. 58 | * @param {Object} obj 59 | * @return {String} 60 | */ 61 | var param = function (obj) { 62 | var query = '', 63 | name, value, fullSubName, subName, subValue, innerObj, i; 64 | 65 | for (name in obj) { 66 | value = obj[name]; 67 | 68 | if (value instanceof Array) { 69 | for (i = 0; i < value.length; ++i) { 70 | subValue = value[i]; 71 | fullSubName = name + '[' + i + ']'; 72 | innerObj = {}; 73 | innerObj[fullSubName] = subValue; 74 | query += param(innerObj) + '&'; 75 | } 76 | } else if (value instanceof Object) { 77 | for (subName in value) { 78 | subValue = value[subName]; 79 | fullSubName = name + '[' + subName + ']'; 80 | innerObj = {}; 81 | innerObj[fullSubName] = subValue; 82 | query += param(innerObj) + '&'; 83 | } 84 | } else if (value !== undefined && value !== null) 85 | query += encodeURIComponent(name) + '=' + encodeURIComponent(value) + '&'; 86 | } 87 | 88 | return query.length ? query.substr(0, query.length - 1) : query; 89 | }; 90 | 91 | // Override $http service's default transformRequest 92 | $httpProvider.defaults.transformRequest = [function (data) { 93 | return angular.isObject(data) && String(data) !== '[object File]' ? param(data) : data; 94 | }]; 95 | /*--end--http post 方法的传参格式更改*/ 96 | 97 | 98 | }]) 99 | .run(['$http', 'CacheFactory', function ($http, CacheFactory) { 100 | $http.defaults.cache = CacheFactory('defaultCache', { 101 | maxAge: 5 * 1000, // Items added to this cache expire after 15 minutes 102 | cacheFlushInterval: 5 * 1000, // This cache will clear itself every hour 103 | deleteOnExpire: 'aggressive' // Items will be deleted from this cache when they expire 104 | }); 105 | }]) 106 | .service("appConfig", ['$cookies', function ($cookies) { 107 | var config = {}; 108 | config.mode = 'dev'; 109 | //config.regionId = $cookies.get("regionId"); 110 | //config.phone = $cookies.get("phone"); 111 | config.providerType = ""; 112 | config.staticServer = ""; 113 | switch (config.mode) { 114 | case 'debug': 115 | config.applianceService = ""; 116 | break; 117 | case 'dev': 118 | config.businessService = ""; 119 | config.orderList = ""; 120 | if ($cookies.get("") == null) 121 | $cookies.put("", ""); 122 | if ($cookies.get("") == null) 123 | 124 | $cookies.put("", ""); 125 | break; 126 | case '': 127 | config.apiService = ""; 128 | config.staticServer = ""; 129 | break; 130 | case '': 131 | config.apiService = ""; 132 | config.staticServer = ""; 133 | break; 134 | } 135 | 136 | return config; 137 | }]); 138 | 139 | -------------------------------------------------------------------------------- /app/app.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = ['$state', '$scope', '$rootScope', function ($state, $scope, $rootScope) { 4 | $rootScope.rows = 8; 5 | $scope.tt = ""; 6 | 7 | $scope.$watch("tt", function (o, n) { 8 | if (o != n) { 9 | $rootScope.signImageUrl = o; 10 | } 11 | }); 12 | 13 | $rootScope.platform_context_path = '/'; 14 | 15 | }] 16 | 17 | -------------------------------------------------------------------------------- /app/app.directive.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = angular.module('app') 3 | .directive('focusInput', ['$ionicScrollDelegate', '$window', '$timeout', '$ionicPosition', function ($ionicScrollDelegate, $window, $timeout, $ionicPosition) { 4 | return { 5 | restrict: 'C', 6 | scope: false, 7 | link: function ($scope, iElm, iAttrs, controller) { 8 | if (ionic.Platform.isIOS()) { 9 | iElm.on('focus', function () { 10 | var top = $ionicScrollDelegate.getScrollPosition().top; 11 | var eleTop = ($ionicPosition.offset(iElm).top) / 2; 12 | var realTop = eleTop + top; 13 | $ionicScrollDelegate.scrollTo(0, realTop); 14 | }) 15 | } 16 | 17 | } 18 | } 19 | }]); 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/assets/fonts/icons_mobiscroll.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/homerious/angular-ionic-webpack/52e44f47e449f170b2126f7d6e52e5c738b9c4c7/app/assets/fonts/icons_mobiscroll.eot -------------------------------------------------------------------------------- /app/assets/fonts/icons_mobiscroll.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Created by FontForge 20120731 at Thu Feb 18 11:48:43 2016 9 | By root 10 | Created by root with FontForge 2.0 (http://fontforge.sf.net) 11 | 12 | 13 | 14 | 27 | 28 | 30 | 35 | 37 | 40 | 43 | 45 | 47 | 49 | 52 | 54 | 59 | 61 | 64 | 70 | 72 | 74 | 76 | 78 | 80 | 86 | 88 | 90 | 93 | 95 | 97 | 99 | 102 | 104 | 106 | 108 | 112 | 114 | 117 | 120 | 122 | 124 | 126 | 128 | 131 | 133 | 135 | 137 | 141 | 144 | 147 | 149 | 151 | 153 | 155 | 158 | 160 | 163 | 166 | 168 | 172 | 174 | 176 | 178 | 180 | 182 | 184 | 186 | 189 | 191 | 193 | 195 | 197 | 199 | 201 | 203 | 205 | 207 | 209 | 211 | 213 | 215 | 218 | 220 | 223 | 225 | 228 | 230 | 232 | 234 | 236 | 238 | 240 | 242 | 245 | 247 | 250 | 253 | 256 | 258 | 260 | 262 | 264 | 268 | 271 | 273 | 276 | 278 | 281 | 283 | 287 | 291 | 292 | 293 | -------------------------------------------------------------------------------- /app/assets/fonts/icons_mobiscroll.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/homerious/angular-ionic-webpack/52e44f47e449f170b2126f7d6e52e5c738b9c4c7/app/assets/fonts/icons_mobiscroll.ttf -------------------------------------------------------------------------------- /app/assets/fonts/icons_mobiscroll.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/homerious/angular-ionic-webpack/52e44f47e449f170b2126f7d6e52e5c738b9c4c7/app/assets/fonts/icons_mobiscroll.woff -------------------------------------------------------------------------------- /app/assets/fonts/ionicons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/homerious/angular-ionic-webpack/52e44f47e449f170b2126f7d6e52e5c738b9c4c7/app/assets/fonts/ionicons.eot -------------------------------------------------------------------------------- /app/assets/fonts/ionicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/homerious/angular-ionic-webpack/52e44f47e449f170b2126f7d6e52e5c738b9c4c7/app/assets/fonts/ionicons.ttf -------------------------------------------------------------------------------- /app/assets/fonts/ionicons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/homerious/angular-ionic-webpack/52e44f47e449f170b2126f7d6e52e5c738b9c4c7/app/assets/fonts/ionicons.woff -------------------------------------------------------------------------------- /app/assets/lib/calendar-pk/release/css/calendar_pk.css: -------------------------------------------------------------------------------- 1 | .calendar-pk { 2 | width: 100%; 3 | max-width: 100%; 4 | background-color: white; 5 | border-top: 1px solid #969696; 6 | border-bottom: 1px solid #969696; 7 | table-layout: fixed; } 8 | .calendar-pk td, 9 | .calendar-pk th { 10 | text-align: center; 11 | vertical-align: middle; } 12 | .calendar-pk > thead > tr { 13 | background-color: #f0f0f0; 14 | border-bottom: 2px solid #969696; } 15 | .calendar-pk > thead > tr > th { 16 | line-height: 2.5em; 17 | border-right: 1px solid #969696; } 18 | .calendar-pk > thead > tr > th:first-child { 19 | border-right: 2px solid #969696; } 20 | .calendar-pk > tbody > tr > td { 21 | border-bottom: 1px solid #969696; 22 | border-right: 1px solid #969696; } 23 | .calendar-pk > tbody > tr > td:first-child { 24 | background-color: #f0f0f0; 25 | border-right: 2px solid #969696; } 26 | .calendar-pk .monthview-primary { 27 | background-color: #d34121; 28 | color: white; } 29 | .calendar-pk .monthview-secondary { 30 | background-color: #e5745b; } 31 | .calendar-pk .monthview-current { 32 | font-weight: bold; 33 | font-size: 1.2em; } 34 | .calendar-pk .text-muted { 35 | color: #323232; } 36 | .click{ 37 | background-color: #81e481; 38 | } 39 | -------------------------------------------------------------------------------- /app/assets/lib/calendar-pk/release/css/calendar_pk.min.css: -------------------------------------------------------------------------------- 1 | .calendar-pk{width:100%;max-width:100%;background-color:#fff;border-top:1px solid #969696;border-bottom:1px solid #969696;table-layout:fixed}.calendar-pk td,.calendar-pk th{text-align:center;vertical-align:middle}.calendar-pk>thead>tr{background-color:#f0f0f0;border-bottom:2px solid #969696}.calendar-pk>thead>tr>th{line-height:2.5em;border-right:1px solid #969696}.calendar-pk>thead>tr>th:first-child{border-right:2px solid #969696}.calendar-pk>tbody>tr>td{border-bottom:1px solid #969696;border-right:1px solid #969696}.calendar-pk>tbody>tr>td:first-child{background-color:#f0f0f0;border-right:2px solid #969696}.calendar-pk .monthview-primary{background-color:#d34121;color:#fff}.calendar-pk .monthview-secondary{background-color:#e5745b}.calendar-pk .monthview-current{font-weight:700;font-size:1.2em}.calendar-pk .text-muted{color:#323232} -------------------------------------------------------------------------------- /app/assets/lib/calendar-pk/release/js/calendar_pk.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('calendar_pk.directives', []); 5 | angular.module('calendar_pk.constants', []); 6 | angular.module('calendar_pk.filters', []); 7 | 8 | var app = angular.module('calendar_pk', ['calendar_pk.directives', 'calendar_pk.constants', 'calendar_pk.templates', 'calendar_pk.filters']); 9 | 10 | })(); 11 | 12 | (function() { 13 | 'use strict'; 14 | 15 | angular.module('calendar_pk.constants') 16 | .constant('calendarConfig', { 17 | formatDay: 'dd', // 18 | formatDayHeader: 'EEE', // 19 | formatMonthTitle: 'yyyy MMMM', // 20 | eventSource: [], // 21 | startingDayMonth: 1, // 22 | }); 23 | 24 | })(); 25 | 26 | (function() { 27 | 'use strict'; 28 | 29 | angular.module('calendar_pk.directives') 30 | .directive('calendarPk', calendarPk); 31 | 32 | calendarPk.$inject = []; 33 | 34 | function calendarPk() { 35 | var directive = {}; 36 | 37 | directive.restrict = 'E'; 38 | directive.replace = true; 39 | 40 | directive.templateUrl = 'calendar-pk.html'; 41 | 42 | directive.scope = { 43 | monthChanged: '&', // Called when changing the month 44 | timeSelected: '&', // Called when clicking on a date 45 | weekSelected: '&', // Called when clicking on a week 46 | eventSource: '=', // All the events => two way data binding 47 | }; 48 | 49 | directive.controllerAs = 'cc'; 50 | directive.controller = CalendarController; 51 | CalendarController.$inject = ['$scope', '$attrs', '$interpolate', 'calendarConfig', '$timeout', '$filter', '$ionicSlideBoxDelegate']; 52 | 53 | function CalendarController($scope, $attrs, $interpolate, calendarConfig, $timeout, $filter, $ionicSlideBoxDelegate) { 54 | var vm = this; 55 | 56 | $scope.$watch(function() { 57 | return $scope.eventSource; 58 | }, function(newVal, oldVal) { 59 | onDataLoaded(); 60 | }, true); 61 | 62 | init(); 63 | 64 | function init() { 65 | // Configuration attributes 66 | angular.forEach(['formatDay', 'formatDayHeader', 'formatMonthTitle', 'startingDayMonth'], function(key, index) { 67 | vm[key] = angular.isDefined($attrs[key]) ? $interpolate($attrs[key])($scope.$parent) : calendarConfig[key]; 68 | }); 69 | 70 | vm.currentViewIndex = 0; 71 | vm.currentDate = new Date(); 72 | vm.isGreen = false; 73 | vm.timecollector = []; 74 | refreshView(); 75 | } 76 | 77 | // Triggered on ng-click when clicking on a date 78 | // => Call the function timeSelected passed to the directive 79 | vm.dayClick = function(selectedTime, $event) { 80 | //toogle(selectedTime); 81 | 82 | if (vm.timecollector.length == 1) { 83 | $event.target.className += " click" 84 | console.log(vm.timecollector.indexOf($event)); 85 | vm.timecollector[0].target.className = vm.timecollector[0].target.className.replace(" click",""); 86 | vm.timecollector = []; 87 | vm.timecollector.push($event); 88 | } else { 89 | console.log(vm.timecollector.indexOf($event)); 90 | $event.target.className += " click"; 91 | vm.timecollector.push($event); 92 | } 93 | 94 | //vm.isGreen = !vm.isGreen; 95 | if ($scope.timeSelected) { 96 | $scope.timeSelected({ selectedTime: selectedTime }); 97 | } 98 | 99 | }; 100 | 101 | // Called when clicking on a week 102 | // => toogle all the week depending if they were all selected 103 | // vm.weekClick = function(date_index) { 104 | // var all_selected = isAllWeekSelected(date_index), 105 | // dates = vm.views[vm.currentViewIndex].dates; 106 | 107 | // for (var i = 0; i < 7; i++) { 108 | // if (all_selected) { 109 | // toogle(dates[date_index + i].date); 110 | // } else if (!all_selected && !dates[date_index + i].event) { 111 | // toogle(dates[date_index + i].date); 112 | // } 113 | // } 114 | 115 | // if ($scope.weekSelected) { 116 | // var monday = dates[date_index].date; 117 | // $scope.weekSelected({monday: monday}); 118 | // } 119 | // }; 120 | 121 | // First called when changing the month 122 | // => calculate the direction of the slide 123 | // => call refreshView 124 | vm.slideChanged = function($index) { 125 | $timeout(function() { 126 | var currentViewIndex = vm.currentViewIndex, 127 | direction = 0; 128 | 129 | if (currentViewIndex === $index - 1 || ($index === 0 && currentViewIndex === 2)) { 130 | direction = 1; 131 | } else if (currentViewIndex === $index + 1 || ($index === 2 && currentViewIndex === 0)) { 132 | direction = -1; 133 | } 134 | 135 | vm.currentViewIndex = $index; 136 | 137 | vm.currentDate = getAdjacentCalendarDate(vm.currentDate, direction); 138 | 139 | refreshView(direction); 140 | }, 100); 141 | }; 142 | 143 | vm.changeColor = function() { 144 | this.j.isGreen = this.j.isGreen; 145 | } 146 | 147 | $scope.$on('changeMonth', function(event, direction) { 148 | var slideHandle = $ionicSlideBoxDelegate.$getByHandle('monthview-slide'); 149 | 150 | if (slideHandle) { 151 | if (direction === 1) { 152 | slideHandle.next(); 153 | } else if (direction === -1) { 154 | slideHandle.previous(); 155 | } 156 | } 157 | }); 158 | 159 | // DONE 160 | function onDataLoaded() { 161 | var eventSource = $scope.eventSource, // All the events 162 | timeZoneOffset = -new Date().getTimezoneOffset(), 163 | utcStartTime = new Date(vm.range.startTime.getTime() + timeZoneOffset * 60 * 1000), // StartTime of the month 164 | utcEndTime = new Date(vm.range.endTime.getTime() + timeZoneOffset * 60 * 1000), // EndTime of the month 165 | dates = vm.views[vm.currentViewIndex].dates; // All the dates of the current scope (42 dates) 166 | 167 | // Reset 168 | for (var r = 0; r < 42; r += 1) { 169 | dates[r].event = false; 170 | } 171 | 172 | // loop over all events 173 | // => If eventDate is in the scope of the current view 174 | // => add the event to vm.views[vm.currentViewIndex].dates 175 | for (var i = 0; i < eventSource.length; i += 1) { 176 | var eventDate = new Date(eventSource[i]); 177 | 178 | if (utcStartTime <= eventDate && eventDate < utcEndTime) { 179 | dates[Math.floor((eventDate - utcStartTime) / 86400000)].event = true; 180 | } 181 | } 182 | } 183 | 184 | // DONE 185 | // From one time, get the corresponding index of the eventSource array 186 | // Used to know if there is a Event at the same date 187 | function getEventIndex(time) { 188 | var j = -1, 189 | eventSource = $scope.eventSource; 190 | 191 | for (var i = 0; i < eventSource.length; i++) { 192 | if (eventSource[i].getDate() === time.getDate() && eventSource[i].getMonth() === time.getMonth() && eventSource[i].getFullYear() === time.getFullYear()) { 193 | j = i; 194 | break; 195 | } 196 | } 197 | 198 | return j; 199 | } 200 | 201 | // DONE 202 | // Used to toogle one date 203 | // TODO => should delete ALL the event for the date => loop ? 204 | function toogle(selectedTime) { 205 | var eventSource = $scope.eventSource, 206 | index = getEventIndex(selectedTime); 207 | 208 | if (eventSource.length > 0) { 209 | eventSource.splice(index, 1); 210 | eventSource.push(selectedTime); 211 | } else { 212 | eventSource.push(selectedTime); 213 | } 214 | // if (index > -1) { 215 | // eventSource.splice(index, 1); 216 | // } else { 217 | // eventSource.push(selectedTime); 218 | // } 219 | } 220 | 221 | // DONE 222 | // Used to get if all the week is selected 223 | function isAllWeekSelected(date_index) { 224 | var dates = vm.views[vm.currentViewIndex].dates, 225 | all_selected = true; 226 | 227 | for (var i = 0; i < 7; i++) { 228 | if (getEventIndex(dates[date_index + i].date) === -1) { 229 | all_selected = false; 230 | break; 231 | } 232 | } 233 | 234 | return all_selected; 235 | } 236 | 237 | // Used to get the "AdjacentCalendarDate" 238 | // => this is the same day as the currentCalendarDate but shifted from one month depending of the direction 239 | // => from the direction, add/substract one month to currentCalendarDate 240 | function getAdjacentCalendarDate(currentCalendarDate, direction) { 241 | var calculateCalendarDate = new Date(currentCalendarDate), 242 | year = calculateCalendarDate.getFullYear(), 243 | month = calculateCalendarDate.getMonth() + direction, 244 | date = calculateCalendarDate.getDate(), 245 | firstDayInNextMonth; 246 | 247 | calculateCalendarDate.setFullYear(year, month, date); 248 | 249 | firstDayInNextMonth = new Date(year, month + 1, 1); 250 | if (firstDayInNextMonth.getTime() <= calculateCalendarDate.getTime()) { 251 | calculateCalendarDate = new Date(firstDayInNextMonth - 24 * 60 * 60 * 1000); 252 | } 253 | 254 | return calculateCalendarDate; 255 | } 256 | 257 | 258 | // Called after changing the month 259 | // direction -1 (gauche), +1(droite) 260 | function refreshView(direction) { 261 | vm.range = getRange(vm.currentDate); 262 | 263 | refreshMonth(); 264 | 265 | populateAdjacentViews(); 266 | 267 | 268 | if ($scope.eventSource) { 269 | onDataLoaded(); 270 | } 271 | 272 | // From one date 273 | // => get the startDate and endDate of new view corresponding to the date 274 | function getRange(date) { 275 | var firstDayOfMonth = new Date(date.getFullYear(), date.getMonth(), 1), 276 | difference = vm.startingDayMonth - firstDayOfMonth.getDay(), // vm.startingDayMonth = 1 (Monday) .getDay() = 0 (SUN) 1 (MON) 2 (TSU) ... 277 | numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : -difference, // number of days to display from previous month 278 | startDate = new Date(firstDayOfMonth), 279 | endDate; 280 | 281 | if (numDisplayedFromPreviousMonth > 0) { 282 | startDate.setDate(-numDisplayedFromPreviousMonth + 1); 283 | } 284 | 285 | endDate = new Date(startDate); 286 | endDate.setDate(endDate.getDate() + 42); 287 | 288 | return { 289 | startTime: startDate, 290 | endTime: endDate 291 | }; 292 | } 293 | 294 | // Used to refresh the month 295 | function refreshMonth() { 296 | var currentViewStartDate = new Date(vm.range.startTime); 297 | 298 | currentViewStartDate.setDate(currentViewStartDate.getDate() + 10); 299 | 300 | // $scope.currentMonth.date = currentViewStartDate; 301 | // $scope.currentMonth.display = $filter('date')(currentViewStartDate, vm.formatMonthTitle); 302 | 303 | if ($scope.monthChanged) { 304 | $scope.monthChanged({ 305 | startTime: vm.range.startTime, 306 | endTime: vm.range.endTime, 307 | display: $filter('date')(currentViewStartDate, vm.formatMonthTitle) 308 | }); 309 | } 310 | } 311 | 312 | function populateAdjacentViews() { 313 | var currentViewStartDate, 314 | currentViewData, 315 | toUpdateViewIndex, 316 | currentViewIndex = vm.currentViewIndex; 317 | 318 | if (direction === 1) { // next month 319 | currentViewStartDate = getAdjacentViewStartTime(direction); 320 | toUpdateViewIndex = (currentViewIndex + 1) % 3; 321 | angular.copy(getDates(currentViewStartDate), vm.views[toUpdateViewIndex]); 322 | } else if (direction === -1) { // previous month 323 | currentViewStartDate = getAdjacentViewStartTime(direction); 324 | toUpdateViewIndex = (currentViewIndex + 2) % 3; 325 | angular.copy(getDates(currentViewStartDate), vm.views[toUpdateViewIndex]); 326 | } else { 327 | if (!vm.views) { 328 | currentViewData = []; 329 | currentViewStartDate = vm.range.startTime; 330 | currentViewData.push(getDates(currentViewStartDate)); 331 | currentViewStartDate = getAdjacentViewStartTime(1); 332 | currentViewData.push(getDates(currentViewStartDate)); 333 | currentViewStartDate = getAdjacentViewStartTime(-1); 334 | currentViewData.push(getDates(currentViewStartDate)); 335 | vm.views = currentViewData; 336 | } else { 337 | currentViewStartDate = vm.range.startTime; 338 | angular.copy(getDates(currentViewStartDate), vm.views[currentViewIndex]); 339 | currentViewStartDate = getAdjacentViewStartTime(-1); 340 | toUpdateViewIndex = (currentViewIndex + 2) % 3; 341 | angular.copy(getDates(currentViewStartDate), vm.views[toUpdateViewIndex]); 342 | currentViewStartDate = getAdjacentViewStartTime(1); 343 | toUpdateViewIndex = (currentViewIndex + 1) % 3; 344 | angular.copy(getDates(currentViewStartDate), vm.views[toUpdateViewIndex]); 345 | } 346 | } 347 | 348 | 349 | // Used to get an array of 42 days (7days * 6weeks) 350 | // => used to display the current month 351 | function getDates(startDate) { 352 | var dates = new Array(42), 353 | current = new Date(startDate); 354 | 355 | current.setHours(12); // Prevent repeated dates because of timezone bug 356 | 357 | for (var i = 0; i < dates.length; i++) { 358 | dates[i] = { 359 | date: new Date(current), 360 | event: false 361 | }; 362 | current.setDate(current.getDate() + 1); 363 | } 364 | 365 | return { 366 | dates: dates 367 | }; 368 | } 369 | 370 | // Used to get the startTime of the view corresponding to the direction and the currentDate 371 | function getAdjacentViewStartTime(direction) { 372 | var adjacentCalendarDate = getAdjacentCalendarDate(vm.currentDate, direction); 373 | return getRange(adjacentCalendarDate).startTime; 374 | } 375 | 376 | // function getAdjacentViewStartTime(direction) { 377 | // // Used to get the "AdjacentCalendarDate" 378 | // // => this is the same day as the currentCalendarDate but shifted from one month depending of the direction 379 | // // => from the direction, add/substract one month to currentCalendarDate 380 | // var calculateCalendarDate = new Date(vm.currentDate), 381 | // year = calculateCalendarDate.getFullYear(), 382 | // month = calculateCalendarDate.getMonth() + direction, 383 | // date = calculateCalendarDate.getDate(), 384 | // firstDayInNextMonth; 385 | 386 | // calculateCalendarDate.setFullYear(year, month, date); 387 | 388 | // firstDayInNextMonth = new Date(year, month + 1, 1); 389 | // if (firstDayInNextMonth.getTime() <= calculateCalendarDate.getTime()) { 390 | // calculateCalendarDate = new Date(firstDayInNextMonth - 24 * 60 * 60 * 1000); 391 | // } 392 | 393 | // return getRange(calculateCalendarDate).startTime; 394 | // } 395 | } 396 | } 397 | } 398 | 399 | return directive; 400 | 401 | } 402 | })(); 403 | 404 | (function() { 405 | angular.module('calendar_pk.filters') 406 | .filter('sameMonth', sameMonth); 407 | 408 | sameMonth.$inject = []; 409 | 410 | function sameMonth() { 411 | return function(date, currentDate) { 412 | date = new Date(+date); 413 | current = new Date(+currentDate); 414 | return (date.getMonth() === current.getMonth() && date.getFullYear() === current.getFullYear()); 415 | }; 416 | 417 | } 418 | })(); 419 | 420 | (function() { 421 | angular.module('calendar_pk.filters') 422 | .filter('todayFilter', todayFilter); 423 | 424 | todayFilter.$inject = []; 425 | 426 | function todayFilter() { 427 | return function(date) { 428 | date = new Date(+date); 429 | var today = new Date(); 430 | return (date.getDate() === today.getDate() && date.getMonth() === today.getMonth() && date.getFullYear() === today.getFullYear()); 431 | }; 432 | 433 | } 434 | })(); 435 | 436 | (function() { 437 | 'use strict'; 438 | 439 | angular.module('calendar_pk.templates', []).run(['$templateCache', function($templateCache) { 440 | $templateCache.put("calendar-pk.html", 441 | "
\n" + 442 | " \n" + 447 | " \n" + 448 | " \n" + 449 | " \n" + 450 | " \n" + 451 | " \n" + 452 | " \n" + 455 | " \n" + 456 | " \n" + 457 | " \n" + 458 | " \n" + 459 | " \n" + 460 | " \n" + 464 | " \n" + 465 | " \n" + 466 | "
\n" + 453 | " {{::day.date | date: cc.formatDayHeader | uppercase}}\n" + 454 | "

{{view.dates[7*i].date | date : 'w'}}周
{{date.date | date : cc.formatDay}}
\n" + 467 | " \n" + 468 | " \n" + 469 | " \n" + 470 | " \n" + 471 | " \n" + 474 | " \n" + 475 | " \n" + 476 | " \n" + 477 | " \n" + 478 | " \n" + 479 | " \n" + 480 | " \n" + 481 | " \n" + 482 | "
\n" + 472 | " {{::day.date | date: cc.formatDayHeader | uppercase}}\n" + 473 | "

{{view.dates[7*i].date | date : 'w'}}周
{{view.dates[7*i+j].date | date : cc.formatDay}}
\n" + 483 | "
\n" + 484 | "
\n" + 485 | "
\n" + 486 | ""); 487 | }]); 488 | }()); 489 | -------------------------------------------------------------------------------- /app/assets/lib/calendar-pk/release/js/calendar_pk.min.js: -------------------------------------------------------------------------------- 1 | !function(){"use strict";angular.module("calendar_pk.directives",[]),angular.module("calendar_pk.constants",[]),angular.module("calendar_pk.filters",[]);angular.module("calendar_pk",["calendar_pk.directives","calendar_pk.constants","calendar_pk.templates","calendar_pk.filters"])}(),function(){"use strict";angular.module("calendar_pk.constants").constant("calendarConfig",{formatDay:"dd",formatDayHeader:"EEE",formatMonthTitle:"MMMM yyyy",eventSource:[],startingDayMonth:1})}(),function(){"use strict";function calendarPk(){function CalendarController($scope,$attrs,$interpolate,calendarConfig,$timeout,$filter,$ionicSlideBoxDelegate){function init(){angular.forEach(["formatDay","formatDayHeader","formatMonthTitle","startingDayMonth"],function(key,index){vm[key]=angular.isDefined($attrs[key])?$interpolate($attrs[key])($scope.$parent):calendarConfig[key]}),vm.currentViewIndex=0,vm.currentDate=new Date,refreshView()}function onDataLoaded(){for(var eventSource=$scope.eventSource,timeZoneOffset=-(new Date).getTimezoneOffset(),utcStartTime=new Date(vm.range.startTime.getTime()+60*timeZoneOffset*1e3),utcEndTime=new Date(vm.range.endTime.getTime()+60*timeZoneOffset*1e3),dates=vm.views[vm.currentViewIndex].dates,r=0;42>r;r+=1)dates[r].event=!1;for(var i=0;i=utcStartTime&&utcEndTime>eventDate&&(dates[Math.floor((eventDate-utcStartTime)/864e5)].event=!0)}}function getEventIndex(time){for(var j=-1,eventSource=$scope.eventSource,i=0;i-1?eventSource.splice(index,1):eventSource.push(selectedTime)}function isAllWeekSelected(date_index){for(var dates=vm.views[vm.currentViewIndex].dates,all_selected=!0,i=0;7>i;i++)if(-1===getEventIndex(dates[date_index+i].date)){all_selected=!1;break}return all_selected}function getAdjacentCalendarDate(currentCalendarDate,direction){var firstDayInNextMonth,calculateCalendarDate=new Date(currentCalendarDate),year=calculateCalendarDate.getFullYear(),month=calculateCalendarDate.getMonth()+direction,date=calculateCalendarDate.getDate();return calculateCalendarDate.setFullYear(year,month,date),firstDayInNextMonth=new Date(year,month+1,1),firstDayInNextMonth.getTime()<=calculateCalendarDate.getTime()&&(calculateCalendarDate=new Date(firstDayInNextMonth-864e5)),calculateCalendarDate}function refreshView(direction){function getRange(date){var endDate,firstDayOfMonth=new Date(date.getFullYear(),date.getMonth(),1),difference=vm.startingDayMonth-firstDayOfMonth.getDay(),numDisplayedFromPreviousMonth=difference>0?7-difference:-difference,startDate=new Date(firstDayOfMonth);return numDisplayedFromPreviousMonth>0&&startDate.setDate(-numDisplayedFromPreviousMonth+1),endDate=new Date(startDate),endDate.setDate(endDate.getDate()+42),{startTime:startDate,endTime:endDate}}function refreshMonth(){var currentViewStartDate=new Date(vm.range.startTime);currentViewStartDate.setDate(currentViewStartDate.getDate()+10),$scope.monthChanged&&$scope.monthChanged({startTime:vm.range.startTime,endTime:vm.range.endTime,display:$filter("date")(currentViewStartDate,vm.formatMonthTitle)})}function populateAdjacentViews(){function getDates(startDate){var dates=new Array(42),current=new Date(startDate);current.setHours(12);for(var i=0;ii;i++)all_selected?toogle(dates[date_index+i].date):all_selected||dates[date_index+i].event||toogle(dates[date_index+i].date);if($scope.weekSelected){var monday=dates[date_index].date;$scope.weekSelected({monday:monday})}},vm.slideChanged=function($index){$timeout(function(){var currentViewIndex=vm.currentViewIndex,direction=0;currentViewIndex===$index-1||0===$index&&2===currentViewIndex?direction=1:(currentViewIndex===$index+1||2===$index&&0===currentViewIndex)&&(direction=-1),vm.currentViewIndex=$index,vm.currentDate=getAdjacentCalendarDate(vm.currentDate,direction),refreshView(direction)},100)},$scope.$on("changeMonth",function(event,direction){var slideHandle=$ionicSlideBoxDelegate.$getByHandle("monthview-slide");slideHandle&&(1===direction?slideHandle.next():-1===direction&&slideHandle.previous())})}var directive={};return directive.restrict="E",directive.replace=!0,directive.templateUrl="calendar-pk.html",directive.scope={monthChanged:"&",timeSelected:"&",weekSelected:"&",eventSource:"="},directive.controllerAs="cc",directive.controller=CalendarController,CalendarController.$inject=["$scope","$attrs","$interpolate","calendarConfig","$timeout","$filter","$ionicSlideBoxDelegate"],directive}angular.module("calendar_pk.directives").directive("calendarPk",calendarPk),calendarPk.$inject=[]}(),function(){function sameMonth(){return function(date,currentDate){return date=new Date(+date),current=new Date(+currentDate),date.getMonth()===current.getMonth()&&date.getFullYear()===current.getFullYear()}}angular.module("calendar_pk.filters").filter("sameMonth",sameMonth),sameMonth.$inject=[]}(),function(){function todayFilter(){return function(date){date=new Date(+date);var today=new Date;return date.getDate()===today.getDate()&&date.getMonth()===today.getMonth()&&date.getFullYear()===today.getFullYear()}}angular.module("calendar_pk.filters").filter("todayFilter",todayFilter),todayFilter.$inject=[]}(),function(){"use strict";angular.module("calendar_pk.templates",[]).run(["$templateCache",function($templateCache){$templateCache.put("calendar-pk.html",'
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n {{::day.date | date: cc.formatDayHeader | uppercase}}\n
SEM
{{view.dates[7*i].date | date : \'w\'}}
{{date.date | date : cc.formatDay}}
\n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n {{::day.date | date: cc.formatDayHeader | uppercase}}\n
SEM
{{view.dates[7*i].date | date : \'w\'}}
{{view.dates[7*i+j].date | date : cc.formatDay}}
\n
\n
\n
\n')}])}(); -------------------------------------------------------------------------------- /app/assets/lib/ionic-datepicker/release/ionic-datepicker.bundle.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){var n=e.createElement("style");if(e.getElementsByTagName("head")[0].appendChild(n),n.styleSheet)n.styleSheet.disabled||(n.styleSheet.cssText=t);else try{n.innerHTML=t}catch(o){n.innerText=t}}(document,".padding_zero {\n padding: 0;\n}\n\n.ionic_datepicker_popup .font_bold {\n font-weight: bold;\n}\n.ionic_datepicker_popup .padding_top_zero {\n padding-top: 0;\n}\n.ionic_datepicker_popup .padding_left_5px {\n padding-left: 5px;\n}\n.ionic_datepicker_popup .padding_right_5px {\n padding-right: 5px;\n}\n.ionic_datepicker_popup .calendar_grid {\n height: 215px;\n}\n.ionic_datepicker_popup .today {\n border: 1px solid #009688;\n border-radius: 50%;\n}\n.ionic_datepicker_popup .selected_date {\n background-color: #009688;\n border-radius: 50%;\n color: #fff;\n font-weight: bold;\n}\n.ionic_datepicker_popup .popup-head {\n background-color: #009688;\n display: none;\n}\n.ionic_datepicker_popup .popup-head .popup-title {\n color: #ffffff;\n}\n.ionic_datepicker_popup .popup-head .popup-sub-title {\n color: #ffffff;\n}\n.ionic_datepicker_popup .popup-body {\n background-color: #ffffff;\n}\n.ionic_datepicker_popup .popup-body .selected_date_full {\n background-color: #019688;\n margin: -10px -10px 0 -10px;\n height: 45px;\n text-align: center;\n font-weight: bold;\n color: #fff;\n line-height: 45px;\n font-size: 18px;\n}\n.ionic_datepicker_popup .popup-body .select_section {\n padding: 1px 5px;\n}\n.ionic_datepicker_popup .popup-body .pointer_events_none {\n pointer-events: none;\n color: #aaaaaa !important;\n}\n.ionic_datepicker_popup .popup-body .month_select, .ionic_datepicker_popup .popup-body .year_select {\n border: none;\n border-bottom: 1px solid #009688;\n padding: 0;\n}\n.ionic_datepicker_popup .popup-body .month_select .input-label, .ionic_datepicker_popup .popup-body .year_select .input-label {\n padding: 2px 0;\n width: 0;\n}\n.ionic_datepicker_popup .popup-body .month_select select, .ionic_datepicker_popup .popup-body .year_select select {\n left: 10px;\n border: none;\n padding: 0;\n}\n.ionic_datepicker_popup .popup-body .month_select:after, .ionic_datepicker_popup .popup-body .year_select:after {\n right: 5px;\n color: #009688;\n}\n.ionic_datepicker_popup .popup-body .show_nav {\n padding: 10px 0 0 0;\n}\n.ionic_datepicker_popup .popup-body .show_nav .prev_btn_section {\n padding: 5px 0;\n text-align: left;\n}\n.ionic_datepicker_popup .popup-body .show_nav .prev_btn_section button {\n padding: 0;\n}\n.ionic_datepicker_popup .popup-body .show_nav .next_btn_section {\n padding: 5px 0;\n text-align: right;\n}\n.ionic_datepicker_popup .popup-body .show_nav .next_btn_section button {\n padding: 0;\n}\n.ionic_datepicker_popup .popup-body .button-clear {\n color: #009688;\n}\n.ionic_datepicker_popup .popup-buttons {\n padding: 0;\n min-height: 45px;\n}\n.ionic_datepicker_popup .popup-buttons button {\n background-color: #009688;\n border-radius: 0;\n margin-right: 1px;\n color: #ffffff;\n}\n\n.ionic_datepicker_modal .header, .ionic_datepicker_modal .footer {\n background-color: #009688;\n}\n.ionic_datepicker_modal .header .title, .ionic_datepicker_modal .header .button, .ionic_datepicker_modal .footer .title, .ionic_datepicker_modal .footer .button {\n color: #ffffff;\n}\n.ionic_datepicker_modal .footer .button-block {\n margin: 0;\n}\n.ionic_datepicker_modal .today {\n border: 1px solid #009688;\n}\n.ionic_datepicker_modal .selected_date {\n background-color: #009688;\n color: #fff;\n font-weight: bold;\n}\n.ionic_datepicker_modal .pointer_events_none {\n pointer-events: none;\n color: #aaaaaa !important;\n}\n.ionic_datepicker_modal .select_section {\n padding: 1px 5px;\n}\n.ionic_datepicker_modal .button-clear {\n color: #009688;\n}\n.ionic_datepicker_modal .month_select, .ionic_datepicker_modal .year_select {\n border: none;\n border-bottom: 1px solid #009688;\n padding: 0;\n}\n.ionic_datepicker_modal .month_select .input-label, .ionic_datepicker_modal .year_select .input-label {\n padding: 2px 0;\n width: 0;\n}\n.ionic_datepicker_modal .month_select select, .ionic_datepicker_modal .year_select select {\n left: 10px;\n border: none;\n padding: 0 10px;\n}\n.ionic_datepicker_modal .month_select:after, .ionic_datepicker_modal .year_select:after {\n right: 5px;\n color: #009688;\n}\n.ionic_datepicker_modal .padding_left_5px {\n padding-left: 5px;\n}\n.ionic_datepicker_modal .padding_right_5px {\n padding-right: 5px;\n}\n.ionic_datepicker_modal .date_col {\n height: 50px;\n line-height: 50px;\n}\n.ionic_datepicker_modal .font_bold {\n font-weight: bold;\n}\n.ionic_datepicker_modal .font_22px {\n font-size: 22px;\n}\n.platform-android .ionic_datepicker_modal .bar .title.title-left {\n text-align: center;\n}\n.platform-android .ionic_datepicker_modal select {\n left: 25%;\n}\n.platform-ios .ionic_datepicker_modal select {\n left: 5%;\n}"),function(e){try{e=angular.module("ionic-datepicker.templates")}catch(t){e=angular.module("ionic-datepicker.templates",[])}e.run(["$templateCache",function(e){e.put("ionic-datepicker-modal.html",'

{{selctedDateEpoch | date : mainObj.dateFormat}}

{{dayList[row + col].date}}
')}])}(),function(e){try{e=angular.module("ionic-datepicker.templates")}catch(t){e=angular.module("ionic-datepicker.templates",[])}e.run(["$templateCache",function(e){e.put("ionic-datepicker-popup.html",'
{{selctedDateEpoch | date : mainObj.dateFormat}}
{{dayList[row + col].date}}
')}])}(),angular.module("ionic-datepicker",["ionic","ionic-datepicker.service","ionic-datepicker.provider","ionic-datepicker.templates"]),angular.module("ionic-datepicker.provider",[]).provider("ionicDatePicker",function(){var e={setLabel:"Set",todayLabel:"Today",closeLabel:"Close",inputDate:new Date,mondayFirst:!0,weeksList:["S","M","T","W","T","F","S"],monthsList:["Jan","Feb","March","April","May","June","July","Aug","Sept","Oct","Nov","Dec"],templateType:"popup",showTodayButton:!1,closeOnSelect:!1,disableWeekdays:[]};this.configDatePicker=function(t){angular.extend(e,t)},this.$get=["$rootScope","$ionicPopup","$ionicModal","IonicDatepickerService",function(t,n,o,a){function i(e){return e.setHours(0),e.setMinutes(0),e.setSeconds(0),e.setMilliseconds(0),e}function c(e){e.disabledDates&&0!==e.disabledDates.length?(u.disabledDates=[],angular.forEach(e.disabledDates,function(e,t){e=i(new Date(e)),u.disabledDates.push(e.getTime())})):u.disabledDates=[]}function d(e){e=i(e),u.currentDate=angular.copy(e);var t=new Date(e.getFullYear(),e.getMonth(),1).getDate(),n=new Date(e.getFullYear(),e.getMonth()+1,0).getDate();u.monthsList=[],u.monthsList=u.mainObj.monthsList&&12===u.mainObj.monthsList.length?u.mainObj.monthsList:a.monthsList,u.yearsList=a.getYearsList(u.mainObj.from,u.mainObj.to),u.dayList=[];var o,c;u.firstDayEpoch=i(new Date(e.getFullYear(),e.getMonth(),t)).getTime(),u.lastDayEpoch=i(new Date(e.getFullYear(),e.getMonth(),n)).getTime();for(var d=t;n>=d;d++)o=new Date(e.getFullYear(),e.getMonth(),d),c=o.getTime()u.toDate||u.mainObj.disableWeekdays.indexOf(o.getDay())>=0,u.dayList.push({date:o.getDate(),month:o.getMonth(),year:o.getFullYear(),day:o.getDay(),epoch:o.getTime(),disabled:c});var r=u.dayList[0].day-u.mainObj.mondayFirst;r=0>r?6:r;for(var p=0;r>p;p++)u.dayList.unshift({});u.rows=[0,7,14,21,28,35],u.cols=[0,1,2,3,4,5,6],u.currentMonth=u.mainObj.monthsList[e.getMonth()],u.currentYear=e.getFullYear(),u.currentMonthSelected=angular.copy(u.currentMonth),u.currentYearSelected=angular.copy(u.currentYear),u.numColumns=7}function r(e){u.mainObj=angular.copy(e),u.selctedDateEpoch=i(u.mainObj.inputDate).getTime(),u.weeksList=u.mainObj.weeksList&&7===u.mainObj.weeksList.length?u.mainObj.weeksList:["S","M","T","W","T","F","S"],u.mainObj.mondayFirst&&u.weeksList.push(u.mainObj.weeksList.shift()),u.disableWeekdays=u.mainObj.disableWeekdays,d(u.mainObj.inputDate),c(u.mainObj)}function p(){u.modal.show()}function l(){u.modal.hide()}var s={},u=t.$new();return u.today=i(new Date).getTime(),u.disabledDates=[],u.prevMonth=function(){1===u.currentDate.getMonth()&&u.currentDate.setFullYear(u.currentDate.getFullYear()),u.currentDate.setMonth(u.currentDate.getMonth()-1),u.currentMonth=u.mainObj.monthsList[u.currentDate.getMonth()],u.currentYear=u.currentDate.getFullYear(),d(u.currentDate)},u.nextMonth=function(){11===u.currentDate.getMonth()&&u.currentDate.setFullYear(u.currentDate.getFullYear()),u.currentDate.setDate(1),u.currentDate.setMonth(u.currentDate.getMonth()+1),u.currentMonth=u.mainObj.monthsList[u.currentDate.getMonth()],u.currentYear=u.currentDate.getFullYear(),d(u.currentDate)},u.dateSelected=function(e){e&&0!==Object.keys(e).length&&(u.selctedDateEpoch=e.epoch,u.mainObj.closeOnSelect&&(u.mainObj.callback(u.selctedDateEpoch),"popup"==u.mainObj.templateType.toLowerCase()?u.popup.close():l()))},u.setIonicDatePickerTodayDate=function(){var e=new Date;d(new Date),u.selctedDateEpoch=i(e).getTime(),u.mainObj.closeOnSelect&&(u.mainObj.callback(u.selctedDateEpoch),l())},u.setIonicDatePickerDate=function(){u.mainObj.callback(u.selctedDateEpoch),l()},u.monthChanged=function(e){var t=u.monthsList.indexOf(e);u.currentDate.setMonth(t),d(u.currentDate)},u.yearChanged=function(e){u.currentDate.setFullYear(e),d(u.currentDate)},o.fromTemplateUrl("ionic-datepicker-modal.html",{scope:u,animation:"slide-in-up"}).then(function(e){u.modal=e}),u.$on("$destroy",function(){u.modal.remove()}),u.closeIonicDatePickerModal=function(){l()},s.openDatePicker=function(t){var o=[];u.mainObj=angular.extend({},e,t),u.mainObj.from&&(u.fromDate=i(new Date(u.mainObj.from)).getTime()),u.mainObj.to&&(u.toDate=i(new Date(u.mainObj.to)).getTime()),t.disableWeekdays&&e.disableWeekdays&&(u.mainObj.disableWeekdays=t.disableWeekdays.concat(e.disableWeekdays)),r(u.mainObj),u.mainObj.closeOnSelect||(o=[{text:u.mainObj.setLabel,type:"button_set",onTap:function(e){u.mainObj.callback(u.selctedDateEpoch)}}]),u.mainObj.showTodayButton&&o.push({text:u.mainObj.todayLabel,type:"button_today",onTap:function(e){var t=new Date;d(new Date),u.selctedDateEpoch=i(t).getTime(),u.mainObj.closeOnSelect||e.preventDefault()}}),o.push({text:u.mainObj.closeLabel,type:"button_close",onTap:function(e){console.log("ionic-datepicker popup closed.")}}),"popup"==u.mainObj.templateType.toLowerCase()?u.popup=n.show({templateUrl:"ionic-datepicker-popup.html",scope:u,cssClass:"ionic_datepicker_popup",buttons:o}):p()},s}]}),angular.module("ionic-datepicker.service",[]).service("IonicDatepickerService",function(){this.monthsList=["January","February","March","April","May","June","July","August","September","October","November","December"],this.getYearsList=function(e,t){var n=[],o=1900,a=2100;o=e?new Date(e).getFullYear():o,a=t?new Date(t).getFullYear():a;for(var i=o;a>=i;i++)n.push(i);return n}}); -------------------------------------------------------------------------------- /app/assets/lib/ionic-filter-bar/release/ionic.filter.bar.css: -------------------------------------------------------------------------------- 1 | .filter-bar-backdrop { 2 | -webkit-transition: opacity 150ms ease-in-out; 3 | transition: opacity 150ms ease-in-out; 4 | opacity: 0; 5 | background-color: rgba(0, 0, 0, 0.4); 6 | position: fixed; 7 | top: 0; 8 | left: 0; 9 | width: 100%; 10 | height: 100%; } 11 | .filter-bar-backdrop.active { 12 | z-index: 10; 13 | opacity: 1; } 14 | 15 | .filter-bar { 16 | position: fixed; 17 | width: 100%; 18 | height: 44px; 19 | z-index: 10; } 20 | .filter-bar .filter-bar-wrapper { 21 | z-index: 11; 22 | position: absolute; 23 | top: 0; 24 | right: 0; 25 | width: 100%; } 26 | .filter-bar .filter-bar-wrapper .item-input-inset .icon.placeholder-icon:before { 27 | padding-top: 3px; 28 | font-size: 16px; } 29 | .filter-bar .filter-bar-wrapper .item-input-inset .item-input-wrapper { 30 | background: #fff; 31 | height: 28px; } 32 | .filter-bar .filter-bar-wrapper .item-input-inset .item-input-wrapper .filter-bar-clear { 33 | padding: 0 2px 0 0; } 34 | .filter-bar .filter-bar-wrapper .item-input-inset .item-input-wrapper .filter-bar-clear:before { 35 | color: #aaa; 36 | font-size: 18px; 37 | padding-top: 1px; } 38 | 39 | .platform-android .filter-bar .filter-bar-light .item-input-wrapper { 40 | border-bottom: 1px solid #ccc; 41 | background: white; } 42 | .platform-android .filter-bar .filter-bar-light .item-input-wrapper input[type="search"] { 43 | color: #444; } 44 | .platform-android .filter-bar .filter-bar-light .item-input-wrapper input[type="search"]::-moz-placeholder { 45 | color: #aaaaaa; } 46 | .platform-android .filter-bar .filter-bar-light .item-input-wrapper input[type="search"]:-ms-input-placeholder { 47 | color: #aaaaaa; } 48 | .platform-android .filter-bar .filter-bar-light .item-input-wrapper input[type="search"]::-webkit-input-placeholder { 49 | color: #aaaaaa; 50 | text-indent: 0; } 51 | .platform-android .filter-bar .filter-bar-light .item-input-wrapper .filter-bar-clear:before { 52 | color: #444; } 53 | .platform-android .filter-bar .filter-bar-stable .item-input-wrapper { 54 | border-bottom: 1px solid #a2a2a2; 55 | background: #f8f8f8; } 56 | .platform-android .filter-bar .filter-bar-stable .item-input-wrapper input[type="search"] { 57 | color: #444; } 58 | .platform-android .filter-bar .filter-bar-stable .item-input-wrapper input[type="search"]::-moz-placeholder { 59 | color: #aaaaaa; } 60 | .platform-android .filter-bar .filter-bar-stable .item-input-wrapper input[type="search"]:-ms-input-placeholder { 61 | color: #aaaaaa; } 62 | .platform-android .filter-bar .filter-bar-stable .item-input-wrapper input[type="search"]::-webkit-input-placeholder { 63 | color: #aaaaaa; 64 | text-indent: 0; } 65 | .platform-android .filter-bar .filter-bar-stable .item-input-wrapper .filter-bar-clear:before { 66 | color: #444; } 67 | .platform-android .filter-bar .filter-bar-positive .item-input-wrapper { 68 | border-bottom: 1px solid #0c60ee; 69 | background: #387ef5; } 70 | .platform-android .filter-bar .filter-bar-positive .item-input-wrapper input[type="search"] { 71 | color: #fff; } 72 | .platform-android .filter-bar .filter-bar-positive .item-input-wrapper input[type="search"]::-moz-placeholder { 73 | color: white; } 74 | .platform-android .filter-bar .filter-bar-positive .item-input-wrapper input[type="search"]:-ms-input-placeholder { 75 | color: white; } 76 | .platform-android .filter-bar .filter-bar-positive .item-input-wrapper input[type="search"]::-webkit-input-placeholder { 77 | color: white; 78 | text-indent: 0; } 79 | .platform-android .filter-bar .filter-bar-positive .item-input-wrapper .filter-bar-clear:before { 80 | color: #fff; } 81 | .platform-android .filter-bar .filter-bar-calm .item-input-wrapper { 82 | border-bottom: 1px solid #0a9dc7; 83 | background: #11c1f3; } 84 | .platform-android .filter-bar .filter-bar-calm .item-input-wrapper input[type="search"] { 85 | color: #fff; } 86 | .platform-android .filter-bar .filter-bar-calm .item-input-wrapper input[type="search"]::-moz-placeholder { 87 | color: white; } 88 | .platform-android .filter-bar .filter-bar-calm .item-input-wrapper input[type="search"]:-ms-input-placeholder { 89 | color: white; } 90 | .platform-android .filter-bar .filter-bar-calm .item-input-wrapper input[type="search"]::-webkit-input-placeholder { 91 | color: white; 92 | text-indent: 0; } 93 | .platform-android .filter-bar .filter-bar-calm .item-input-wrapper .filter-bar-clear:before { 94 | color: #fff; } 95 | .platform-android .filter-bar .filter-bar-assertive .item-input-wrapper { 96 | border-bottom: 1px solid #e42112; 97 | background: #ef473a; } 98 | .platform-android .filter-bar .filter-bar-assertive .item-input-wrapper input[type="search"] { 99 | color: #fff; } 100 | .platform-android .filter-bar .filter-bar-assertive .item-input-wrapper input[type="search"]::-moz-placeholder { 101 | color: white; } 102 | .platform-android .filter-bar .filter-bar-assertive .item-input-wrapper input[type="search"]:-ms-input-placeholder { 103 | color: white; } 104 | .platform-android .filter-bar .filter-bar-assertive .item-input-wrapper input[type="search"]::-webkit-input-placeholder { 105 | color: white; 106 | text-indent: 0; } 107 | .platform-android .filter-bar .filter-bar-assertive .item-input-wrapper .filter-bar-clear:before { 108 | color: #fff; } 109 | .platform-android .filter-bar .filter-bar-balanced .item-input-wrapper { 110 | border-bottom: 1px solid #28a54c; 111 | background: #33cd5f; } 112 | .platform-android .filter-bar .filter-bar-balanced .item-input-wrapper input[type="search"] { 113 | color: #fff; } 114 | .platform-android .filter-bar .filter-bar-balanced .item-input-wrapper input[type="search"]::-moz-placeholder { 115 | color: white; } 116 | .platform-android .filter-bar .filter-bar-balanced .item-input-wrapper input[type="search"]:-ms-input-placeholder { 117 | color: white; } 118 | .platform-android .filter-bar .filter-bar-balanced .item-input-wrapper input[type="search"]::-webkit-input-placeholder { 119 | color: white; 120 | text-indent: 0; } 121 | .platform-android .filter-bar .filter-bar-balanced .item-input-wrapper .filter-bar-clear:before { 122 | color: #fff; } 123 | .platform-android .filter-bar .filter-bar-energized .item-input-wrapper { 124 | border-bottom: 1px solid #e6b500; 125 | background: #ffc900; } 126 | .platform-android .filter-bar .filter-bar-energized .item-input-wrapper input[type="search"] { 127 | color: #fff; } 128 | .platform-android .filter-bar .filter-bar-energized .item-input-wrapper input[type="search"]::-moz-placeholder { 129 | color: white; } 130 | .platform-android .filter-bar .filter-bar-energized .item-input-wrapper input[type="search"]:-ms-input-placeholder { 131 | color: white; } 132 | .platform-android .filter-bar .filter-bar-energized .item-input-wrapper input[type="search"]::-webkit-input-placeholder { 133 | color: white; 134 | text-indent: 0; } 135 | .platform-android .filter-bar .filter-bar-energized .item-input-wrapper .filter-bar-clear:before { 136 | color: #fff; } 137 | .platform-android .filter-bar .filter-bar-royal .item-input-wrapper { 138 | border-bottom: 1px solid #6b46e5; 139 | background: #886aea; } 140 | .platform-android .filter-bar .filter-bar-royal .item-input-wrapper input[type="search"] { 141 | color: #fff; } 142 | .platform-android .filter-bar .filter-bar-royal .item-input-wrapper input[type="search"]::-moz-placeholder { 143 | color: white; } 144 | .platform-android .filter-bar .filter-bar-royal .item-input-wrapper input[type="search"]:-ms-input-placeholder { 145 | color: white; } 146 | .platform-android .filter-bar .filter-bar-royal .item-input-wrapper input[type="search"]::-webkit-input-placeholder { 147 | color: white; 148 | text-indent: 0; } 149 | .platform-android .filter-bar .filter-bar-royal .item-input-wrapper .filter-bar-clear:before { 150 | color: #fff; } 151 | .platform-android .filter-bar .filter-bar-dark .item-input-wrapper { 152 | border-bottom: 1px solid #000; 153 | background: #444444; } 154 | .platform-android .filter-bar .filter-bar-dark .item-input-wrapper input[type="search"] { 155 | color: #fff; } 156 | .platform-android .filter-bar .filter-bar-dark .item-input-wrapper input[type="search"]::-moz-placeholder { 157 | color: white; } 158 | .platform-android .filter-bar .filter-bar-dark .item-input-wrapper input[type="search"]:-ms-input-placeholder { 159 | color: white; } 160 | .platform-android .filter-bar .filter-bar-dark .item-input-wrapper input[type="search"]::-webkit-input-placeholder { 161 | color: white; 162 | text-indent: 0; } 163 | .platform-android .filter-bar .filter-bar-dark .item-input-wrapper .filter-bar-clear:before { 164 | color: #fff; } 165 | .platform-android .filter-bar .filter-bar-default .item-input-wrapper { 166 | border-bottom: 1px solid #ccc; 167 | background: white; } 168 | .platform-android .filter-bar .filter-bar-default .item-input-wrapper input[type="search"] { 169 | color: #444; } 170 | .platform-android .filter-bar .filter-bar-default .item-input-wrapper input[type="search"]::-moz-placeholder { 171 | color: #aaaaaa; } 172 | .platform-android .filter-bar .filter-bar-default .item-input-wrapper input[type="search"]:-ms-input-placeholder { 173 | color: #aaaaaa; } 174 | .platform-android .filter-bar .filter-bar-default .item-input-wrapper input[type="search"]::-webkit-input-placeholder { 175 | color: #aaaaaa; 176 | text-indent: 0; } 177 | .platform-android .filter-bar .filter-bar-default .item-input-wrapper .filter-bar-clear:before { 178 | color: #444; } 179 | .platform-android .filter-bar-wrapper .item-input-inset { 180 | padding-right: 24px; } 181 | .platform-android .filter-bar-wrapper .item-input-inset .filter-bar-cancel { 182 | padding-left: 0; } 183 | .platform-android .filter-bar-wrapper .item-input-inset .filter-bar-cancel:before { 184 | font-size: 24px; } 185 | .platform-android .filter-bar-wrapper .item-input-inset .item-input-wrapper { 186 | border-radius: 0; 187 | padding-left: 0; 188 | margin-left: 10px; } 189 | .platform-android .filter-bar-wrapper .item-input-inset .item-input-wrapper input[type="search"] { 190 | font-weight: 500; } 191 | .platform-android .filter-bar-wrapper .item-input-inset .item-input-wrapper .filter-bar-clear:before { 192 | font-size: 20px; } 193 | 194 | .filter-bar-transition-horizontal { 195 | -webkit-transition: -webkit-transform cubic-bezier(.25, .45, .05, 1) 300ms; 196 | transition: transform cubic-bezier(.25, .45, .05, 1) 300ms; 197 | -webkit-transform: translate3d(100%, 0, 0); 198 | transform: translate3d(100%, 0, 0); } 199 | 200 | .filter-bar-transition-vertical { 201 | -webkit-transition: -webkit-transform cubic-bezier(.25, .45, .05, 1) 350ms; 202 | transition: transform cubic-bezier(.25, .45, .05, 1) 350ms; 203 | -webkit-transform: translate3d(0, -100%, 0); 204 | transform: translate3d(0, -100%, 0); } 205 | 206 | .filter-bar-transition-fade { 207 | -webkit-transition: opacity 250ms ease-in-out; 208 | transition: opacity 250ms ease-in-out; 209 | opacity: 0; } 210 | 211 | .filter-bar-in { 212 | -webkit-transform: translate3d(0, 0, 0); 213 | transform: translate3d(0, 0, 0); 214 | opacity: 1; } 215 | 216 | .filter-bar-modal .item.item-input { 217 | padding-right: 16px; } 218 | .filter-bar-modal .list-right-editing .item.item-input { 219 | opacity: .5; } 220 | .filter-bar-modal .button.button-icon.ion-ios-checkmark-empty:before { 221 | font-size: 42px; } 222 | 223 | .filter-bar-element-hide { 224 | display: none; } 225 | -------------------------------------------------------------------------------- /app/assets/lib/ionic-filter-bar/release/ionic.filter.bar.js: -------------------------------------------------------------------------------- 1 | angular.module('jett.ionic.filter.bar', ['ionic']); 2 | (function (angular, document) { 3 | 'use strict'; 4 | 5 | angular.module('jett.ionic.filter.bar') 6 | .directive('ionFilterBar', [ 7 | '$timeout', 8 | '$ionicGesture', 9 | '$ionicPlatform', 10 | function ($timeout, $ionicGesture, $ionicPlatform) { 11 | var filterBarTemplate; 12 | 13 | //create platform specific filterBar template using filterConfig items 14 | if ($ionicPlatform.is('android')) { 15 | filterBarTemplate = 16 | '
' + 17 | '
' + 18 | '' + 19 | '' + 23 | '
' + 24 | '
'; 25 | } else { 26 | filterBarTemplate = 27 | '
' + 28 | '
' + 29 | '' + 34 | '' + 35 | '
' + 36 | '
'; 37 | } 38 | 39 | return { 40 | restrict: 'E', 41 | scope: true, 42 | link: function ($scope, $element) { 43 | var el = $element[0]; 44 | var clearEl = el.querySelector('.filter-bar-clear'); 45 | var cancelEl = el.querySelector('.filter-bar-cancel'); 46 | var inputEl = el.querySelector('.filter-bar-search'); 47 | var filterTextTimeout; 48 | var swipeGesture; 49 | var backdrop; 50 | var backdropClick; 51 | var filterWatch; 52 | 53 | // Action when filter bar is cancelled via backdrop click/swipe or cancel/back buton click. 54 | // Invokes cancel function defined in filterBar service 55 | var cancelFilterBar = function () { 56 | $scope.cancelFilterBar(); 57 | }; 58 | 59 | // If backdrop is enabled, create and append it to filter, then add click/swipe listeners to cancel filter 60 | if ($scope.config.backdrop) { 61 | backdrop = angular.element('
'); 62 | $element.append(backdrop); 63 | 64 | backdropClick = function(e) { 65 | if (e.target == backdrop[0]) { 66 | cancelFilterBar(); 67 | } 68 | }; 69 | 70 | backdrop.bind('click', backdropClick); 71 | swipeGesture = $ionicGesture.on('swipe', backdropClick, backdrop); 72 | } 73 | 74 | //Sure we could have had 1 function that also checked for favoritesEnabled.. but no need to keep checking a var that wont change 75 | if ($scope.favoritesEnabled) { 76 | $scope.getClearButtonClass = function () { 77 | return $scope.data.filterText.length ? $scope.config.clear : $scope.config.favorite; 78 | } 79 | } else { 80 | $scope.getClearButtonClass = function () { 81 | return $scope.data.filterText.length ? $scope.config.clear : 'filter-bar-element-hide'; 82 | } 83 | } 84 | 85 | // When clear button is clicked, clear filterText, hide clear button, show backdrop, and focus the input 86 | var clearClick = function () { 87 | if (clearEl.classList.contains($scope.config.favorite)) { 88 | $scope.showModal(); 89 | } else { 90 | $timeout(function () { 91 | $scope.data.filterText = ''; 92 | ionic.requestAnimationFrame(function () { 93 | $scope.showBackdrop(); 94 | $scope.scrollItemsTop(); 95 | $scope.focusInput(); 96 | }); 97 | }); 98 | } 99 | }; 100 | 101 | // Bind touchstart so we can regain focus of input even while scrolling 102 | var inputClick = function () { 103 | $scope.scrollItemsTop(); 104 | $scope.focusInput(); 105 | }; 106 | 107 | // When a non escape key is pressed, show/hide backdrop/clear button based on filterText length 108 | var keyUp = function(e) { 109 | if (e.which == 27) { 110 | cancelFilterBar(); 111 | } else if ($scope.data.filterText && $scope.data.filterText.length) { 112 | $scope.hideBackdrop(); 113 | } else { 114 | $scope.showBackdrop(); 115 | } 116 | }; 117 | 118 | //Event Listeners 119 | cancelEl.addEventListener('click', cancelFilterBar); 120 | // Since we are wrapping with label, need to bind touchstart rather than click. 121 | // Even if we use div instead of label need to bind touchstart. Click isn't allowing input to regain focus quickly 122 | clearEl.addEventListener('touchstart', clearClick); 123 | clearEl.addEventListener('mousedown', clearClick); 124 | 125 | inputEl.addEventListener('touchstart', inputClick); 126 | inputEl.addEventListener('mousedown', inputClick); 127 | 128 | document.addEventListener('keyup', keyUp); 129 | 130 | // Calls the services filterItems function with the filterText to filter items 131 | var filterItems = function () { 132 | $scope.filterItems($scope.data.filterText); 133 | }; 134 | 135 | // Clean up when scope is destroyed 136 | $scope.$on('$destroy', function() { 137 | $element.remove(); 138 | document.removeEventListener('keyup', keyUp); 139 | if (backdrop) { 140 | $ionicGesture.off(swipeGesture, 'swipe', backdropClick); 141 | } 142 | filterWatch(); 143 | }); 144 | 145 | // Watch for changes on filterText and call filterItems when filterText has changed. 146 | // If debounce is enabled, filter items by the specified or default delay. 147 | // Prefer timeout debounce over ng-model-options so if filterText is cleared, initial items show up right away with no delay 148 | filterWatch = $scope.$watch('data.filterText', function (newFilterText, oldFilterText) { 149 | var delay; 150 | 151 | if (filterTextTimeout) { 152 | $timeout.cancel(filterTextTimeout); 153 | } 154 | 155 | if (newFilterText !== oldFilterText) { 156 | delay = (newFilterText.length && $scope.debounce) ? $scope.delay : 0; 157 | filterTextTimeout = $timeout(filterItems, delay, false); 158 | } 159 | }); 160 | }, 161 | template: filterBarTemplate 162 | }; 163 | }]); 164 | 165 | })(angular, document); 166 | 167 | /* global angular */ 168 | /** 169 | * This copies the functionality of the ionicConfig provider to allow for platform specific configuration 170 | */ 171 | (function (angular) { 172 | 'use strict'; 173 | 174 | angular.module('jett.ionic.filter.bar') 175 | .provider('$ionicFilterBarConfig', function () { 176 | 177 | var provider = this; 178 | provider.platform = {}; 179 | var PLATFORM = 'platform'; 180 | 181 | var configProperties = { 182 | theme: PLATFORM, 183 | clear: PLATFORM, 184 | add: PLATFORM, 185 | close: PLATFORM, 186 | done: PLATFORM, 187 | remove: PLATFORM, 188 | reorder: PLATFORM, 189 | favorite: PLATFORM, 190 | search: PLATFORM, 191 | backdrop: PLATFORM, 192 | transition: PLATFORM, 193 | platform: {}, 194 | placeholder: PLATFORM 195 | }; 196 | 197 | createConfig(configProperties, provider, ''); 198 | 199 | // Default 200 | // ------------------------- 201 | setPlatformConfig('default', { 202 | clear: 'ion-ios-close', 203 | add: 'ion-ios-plus-outline', 204 | close: 'ion-ios-close-empty', 205 | done: 'ion-ios-checkmark-empty', 206 | remove: 'ion-ios-trash-outline', 207 | reorder: 'ion-drag', 208 | favorite: 'ion-ios-star', 209 | search: 'ion-ios-search-strong', 210 | backdrop: true, 211 | transition: 'vertical', 212 | placeholder: 'Search' 213 | }); 214 | 215 | // iOS (it is the default already) 216 | // ------------------------- 217 | setPlatformConfig('ios', {}); 218 | 219 | // Android 220 | // ------------------------- 221 | setPlatformConfig('android', { 222 | clear: 'ion-android-close', 223 | close: 'ion-android-close', 224 | done: 'ion-android-done', 225 | remove: 'ion-android-delete', 226 | favorite: 'ion-android-star', 227 | search: false, 228 | backdrop: false, 229 | transition: 'horizontal' 230 | }); 231 | 232 | provider.setPlatformConfig = setPlatformConfig; 233 | 234 | // private: used to set platform configs 235 | function setPlatformConfig(platformName, platformConfigs) { 236 | configProperties.platform[platformName] = platformConfigs; 237 | provider.platform[platformName] = {}; 238 | 239 | addConfig(configProperties, configProperties.platform[platformName]); 240 | 241 | createConfig(configProperties.platform[platformName], provider.platform[platformName], ''); 242 | } 243 | 244 | // private: used to recursively add new platform configs 245 | function addConfig(configObj, platformObj) { 246 | for (var n in configObj) { 247 | if (n != PLATFORM && configObj.hasOwnProperty(n)) { 248 | if (angular.isObject(configObj[n])) { 249 | if (!angular.isDefined(platformObj[n])) { 250 | platformObj[n] = {}; 251 | } 252 | addConfig(configObj[n], platformObj[n]); 253 | 254 | } else if (!angular.isDefined(platformObj[n])) { 255 | platformObj[n] = null; 256 | } 257 | } 258 | } 259 | } 260 | 261 | // private: create methods for each config to get/set 262 | function createConfig(configObj, providerObj, platformPath) { 263 | angular.forEach(configObj, function(value, namespace) { 264 | 265 | if (angular.isObject(configObj[namespace])) { 266 | // recursively drill down the config object so we can create a method for each one 267 | providerObj[namespace] = {}; 268 | createConfig(configObj[namespace], providerObj[namespace], platformPath + '.' + namespace); 269 | 270 | } else { 271 | // create a method for the provider/config methods that will be exposed 272 | providerObj[namespace] = function(newValue) { 273 | if (arguments.length) { 274 | configObj[namespace] = newValue; 275 | return providerObj; 276 | } 277 | if (configObj[namespace] == PLATFORM) { 278 | // if the config is set to 'platform', then get this config's platform value 279 | var platformConfig = stringObj(configProperties.platform, ionic.Platform.platform() + platformPath + '.' + namespace); 280 | if (platformConfig || platformConfig === false) { 281 | return platformConfig; 282 | } 283 | // didnt find a specific platform config, now try the default 284 | return stringObj(configProperties.platform, 'default' + platformPath + '.' + namespace); 285 | } 286 | return configObj[namespace]; 287 | }; 288 | } 289 | 290 | }); 291 | } 292 | 293 | //splits a string by dot operator and accesses the end var. For example in a.b.c, 294 | function stringObj(obj, str) { 295 | str = str.split("."); 296 | for (var i = 0; i < str.length; i++) { 297 | if (obj && angular.isDefined(obj[str[i]])) { 298 | obj = obj[str[i]]; 299 | } else { 300 | return null; 301 | } 302 | } 303 | return obj; 304 | } 305 | 306 | provider.$get = function() { 307 | return provider; 308 | }; 309 | 310 | }); 311 | 312 | })(angular); 313 | 314 | /* global angular,ionic */ 315 | /** 316 | * @ngdoc service 317 | * @name $ionicFilterBar 318 | * @module ionic 319 | * @description The Filter Bar is an animated bar that allows a user to search or filter an array of items. 320 | */ 321 | (function (angular, ionic) { 322 | 'use strict'; 323 | 324 | var filterBarModalTemplate = 325 | '' + 326 | '' + 327 | '' + 328 | '

' + 329 | '' + 330 | '
' + 331 | '' + 332 | '' + 333 | '' + 334 | '' + 335 | '' + 336 | '' + 337 | '' + 338 | '
' + 339 | '' + 340 | '' + 341 | '
' + 342 | '
' + 343 | '
' + 344 | '
'; 345 | 346 | var getNavBarTheme = function ($navBar) { 347 | var themes = ['light', 'stable', 'positive', 'calm', 'balanced', 'energized', 'assertive', 'royal', 'dark']; 348 | var classList = $navBar && $navBar.classList; 349 | 350 | if (!classList) { 351 | return; 352 | } 353 | 354 | for (var i = 0; i < themes.length; i++) { 355 | if (classList.contains('bar-' + themes[i])) { 356 | return themes[i]; 357 | } 358 | } 359 | }; 360 | 361 | angular.module('jett.ionic.filter.bar') 362 | .factory('$ionicFilterBar', [ 363 | '$document', 364 | '$rootScope', 365 | '$compile', 366 | '$timeout', 367 | '$filter', 368 | '$ionicPlatform', 369 | '$ionicFilterBarConfig', 370 | '$ionicConfig', 371 | '$ionicModal', 372 | '$ionicScrollDelegate', 373 | function ($document, $rootScope, $compile, $timeout, $filter, $ionicPlatform, $ionicFilterBarConfig, $ionicConfig, $ionicModal, $ionicScrollDelegate) { 374 | var isShown = false; 375 | var $body = $document[0].body; 376 | var templateConfig = { 377 | theme: $ionicFilterBarConfig.theme(), 378 | transition: $ionicFilterBarConfig.transition(), 379 | back: $ionicConfig.backButton.icon(), 380 | clear: $ionicFilterBarConfig.clear(), 381 | favorite: $ionicFilterBarConfig.favorite(), 382 | search: $ionicFilterBarConfig.search(), 383 | backdrop: $ionicFilterBarConfig.backdrop(), 384 | placeholder: $ionicFilterBarConfig.placeholder(), 385 | close: $ionicFilterBarConfig.close(), 386 | done: $ionicFilterBarConfig.done(), 387 | reorder: $ionicFilterBarConfig.reorder(), 388 | remove: $ionicFilterBarConfig.remove(), 389 | add: $ionicFilterBarConfig.add() 390 | }; 391 | 392 | /** 393 | * @ngdoc method 394 | * @name $ionicFilterBar#show 395 | * @description 396 | * Load and return a new filter bar. 397 | * 398 | * A new isolated scope will be created for the filter bar and the new filter bar will be appended to the 399 | * body, covering the header bar. 400 | * 401 | * @returns {function} `hideFilterBar` A function which, when called, hides & cancels the filter bar. 402 | */ 403 | function filterBar (opts) { 404 | //if filterBar is already shown return 405 | if (isShown) { 406 | return; 407 | } 408 | 409 | isShown = true; 410 | opts = opts || {}; 411 | 412 | var scope = $rootScope.$new(true); 413 | var backdropShown = false; 414 | var isKeyboardShown = false; 415 | 416 | //if container option is set, determine the container element by querying for the container class 417 | if (opts.container) { 418 | opts.container = $body.querySelector(opts.container); 419 | } 420 | 421 | //extend scope defaults with supplied options 422 | angular.extend(scope, { 423 | config: templateConfig, 424 | $deregisterBackButton: angular.noop, 425 | update: angular.noop, 426 | cancel: angular.noop, 427 | done: angular.noop, 428 | scrollDelegate: $ionicScrollDelegate, 429 | filter: $filter('filter'), 430 | filterProperties: null, 431 | expression: null, 432 | comparator: null, 433 | debounce: true, 434 | delay: 300, 435 | cancelText: 'Cancel', 436 | cancelOnStateChange: true, 437 | container: $body, 438 | favoritesTitle: 'Favorite Searches', 439 | favoritesAddPlaceholder: 'Add a search term', 440 | favoritesEnabled: false, 441 | favoritesKey: 'ionic_filter_bar_favorites' 442 | }, opts); 443 | 444 | scope.data = {filterText: ''}; 445 | 446 | //if no custom theme was configured, get theme of containers bar-header 447 | if (!scope.config.theme) { 448 | scope.config.theme = getNavBarTheme(scope.container.querySelector('.bar.bar-header')); 449 | } 450 | 451 | // Compile the template 452 | var element = scope.element = $compile('')(scope); 453 | 454 | // Grab required jQLite elements 455 | var filterWrapperEl = element.children().eq(0); 456 | var input = filterWrapperEl.find('input')[0]; 457 | var backdropEl = element.children().eq(1); 458 | 459 | //get scrollView 460 | var scrollView = scope.scrollDelegate.getScrollView(); 461 | var canScroll = !!scrollView; 462 | 463 | //get the scroll container if scrolling is available 464 | var $scrollContainer = canScroll ? scrollView.__container : null; 465 | 466 | var stateChangeListenDone = scope.cancelOnStateChange ? 467 | $rootScope.$on('$stateChangeSuccess', function () { scope.cancelFilterBar(); }) : 468 | angular.noop; 469 | 470 | // Focus the input which will show the keyboard. 471 | var showKeyboard = function () { 472 | if (!isKeyboardShown) { 473 | isKeyboardShown = true; 474 | input && input.focus(); 475 | } 476 | }; 477 | 478 | // Blur the input which will hide the keyboard. 479 | // Even if we need to bring in ionic.keyboard in the future, blur is preferred so keyboard animates out. 480 | var hideKeyboard = function () { 481 | if (isKeyboardShown) { 482 | isKeyboardShown = false; 483 | input && input.blur(); 484 | } 485 | }; 486 | 487 | // When the filtered list is scrolled, we want to hide the keyboard as long as it's not already hidden 488 | var handleScroll = function () { 489 | if (scrollView.__scrollTop > 0) { 490 | hideKeyboard(); 491 | } 492 | }; 493 | 494 | // Scrolls the list of items to the top via the scroll delegate 495 | scope.scrollItemsTop = function () { 496 | if (canScroll && scrollView.__scrollTop > 0 && scope.scrollDelegate.scrollTop) { 497 | scope.scrollDelegate.scrollTop(); 498 | } 499 | }; 500 | 501 | // Set isKeyboardShown to force showing keyboard on search focus. 502 | scope.focusInput = function () { 503 | isKeyboardShown = false; 504 | showKeyboard(); 505 | }; 506 | 507 | // Hide the filterBar backdrop if in the DOM and not already hidden. 508 | scope.hideBackdrop = function () { 509 | if (backdropEl.length && backdropShown) { 510 | backdropShown = false; 511 | backdropEl.removeClass('active').css('display', 'none'); 512 | } 513 | }; 514 | 515 | // Show the filterBar backdrop if in the DOM and not already shown. 516 | scope.showBackdrop = function () { 517 | if (backdropEl.length && !backdropShown) { 518 | backdropShown = true; 519 | backdropEl.css('display', 'block').addClass('active'); 520 | } 521 | }; 522 | 523 | scope.showModal = function () { 524 | scope.modal = $ionicModal.fromTemplate(filterBarModalTemplate, { 525 | scope: scope 526 | }); 527 | scope.modal.show(); 528 | }; 529 | 530 | // Filters the supplied list of items via the supplied filterText. 531 | // How items are filtered depends on the supplied filter object, and expression 532 | // Filtered items will be sent to update 533 | scope.filterItems = function(filterText) { 534 | var filterExp, filteredItems; 535 | 536 | // pass back original list if filterText is empty. 537 | // Otherwise filter by expression, supplied properties, or filterText. 538 | if (!filterText.length) { 539 | filteredItems = scope.items; 540 | } else { 541 | if (scope.expression) { 542 | filterExp = angular.bind(this, scope.expression, filterText); 543 | } else if (angular.isArray(scope.filterProperties)) { 544 | filterExp = {}; 545 | angular.forEach(scope.filterProperties, function (property) { 546 | filterExp[property] = filterText; 547 | }); 548 | } else if (scope.filterProperties) { 549 | filterExp = {}; 550 | filterExp[scope.filterProperties] = filterText; 551 | } else { 552 | filterExp = filterText; 553 | } 554 | 555 | filteredItems = scope.filter(scope.items, filterExp, scope.comparator); 556 | } 557 | 558 | $timeout(function() { 559 | scope.update(filteredItems, filterText); 560 | scope.scrollItemsTop(); 561 | }); 562 | }; 563 | 564 | // registerBackButtonAction returns a callback to deregister the action 565 | scope.$deregisterBackButton = $ionicPlatform.registerBackButtonAction( 566 | function() { 567 | $timeout(scope.cancelFilterBar); 568 | }, 300 569 | ); 570 | 571 | // Removes the filterBar from the body and cleans up vars/events. Once the backdrop is hidden we can invoke done 572 | scope.removeFilterBar = function(done) { 573 | if (scope.removed) return; 574 | 575 | scope.removed = true; 576 | 577 | //animate the filterBar out, hide keyboard and backdrop 578 | ionic.requestAnimationFrame(function () { 579 | filterWrapperEl.removeClass('filter-bar-in'); 580 | hideKeyboard(); 581 | scope.hideBackdrop(); 582 | 583 | //Wait before cleaning up so element isn't removed before filter bar animates out 584 | $timeout(function () { 585 | scope.scrollItemsTop(); 586 | scope.update(scope.items); 587 | 588 | scope.$destroy(); 589 | element.remove(); 590 | scope.cancelFilterBar.$scope = scope.modal = $scrollContainer = scrollView = filterWrapperEl = backdropEl = input = null; 591 | isShown = false; 592 | (done || angular.noop)(); 593 | }, 350); 594 | }); 595 | 596 | $timeout(function () { 597 | // wait to remove this due to a 300ms delay native 598 | // click which would trigging whatever was underneath this 599 | scope.container.classList.remove('filter-bar-open'); 600 | }, 400); 601 | 602 | scope.$deregisterBackButton(); 603 | stateChangeListenDone(); 604 | 605 | //unbind scroll event 606 | if ($scrollContainer) { 607 | $scrollContainer.removeEventListener('scroll', handleScroll); 608 | } 609 | }; 610 | 611 | // Appends the filterBar to the body. Once the backdrop is hidden we can invoke done 612 | scope.showFilterBar = function(done) { 613 | if (scope.removed) return; 614 | 615 | scope.container.appendChild(element[0]); 616 | scope.container.classList.add('filter-bar-open'); 617 | 618 | //scroll items to the top before starting the animation 619 | scope.scrollItemsTop(); 620 | 621 | //start filterBar animation, show backrop and focus the input 622 | ionic.requestAnimationFrame(function () { 623 | if (scope.removed) return; 624 | 625 | $timeout(function () { 626 | filterWrapperEl.addClass('filter-bar-in'); 627 | scope.focusInput(); 628 | scope.showBackdrop(); 629 | (done || angular.noop)(); 630 | }, 20, false); 631 | }); 632 | 633 | if ($scrollContainer) { 634 | $scrollContainer.addEventListener('scroll', handleScroll); 635 | } 636 | }; 637 | 638 | // called when the user presses the backdrop, cancel/back button, changes state 639 | scope.cancelFilterBar = function() { 640 | // after the animation is out, call the cancel callback 641 | scope.removeFilterBar(scope.cancel); 642 | }; 643 | 644 | scope.showFilterBar(scope.done); 645 | 646 | // Expose the scope on $ionFilterBar's return value for the sake of testing it. 647 | scope.cancelFilterBar.$scope = scope; 648 | 649 | return scope.cancelFilterBar; 650 | } 651 | 652 | return { 653 | show: filterBar 654 | }; 655 | }]); 656 | 657 | 658 | })(angular, ionic); 659 | 660 | /* global angular */ 661 | (function (angular) { 662 | 'use strict'; 663 | 664 | angular.module('jett.ionic.filter.bar') 665 | .controller('$ionicFilterBarModalCtrl', [ 666 | '$window', 667 | '$scope', 668 | '$timeout', 669 | '$ionicListDelegate', 670 | function ($window, $scope, $timeout, $ionicListDelegate) { 671 | var searchesKey = $scope.$parent.favoritesKey; 672 | 673 | $scope.displayData = {showReorder: false}; 674 | $scope.searches = angular.fromJson($window.localStorage.getItem(searchesKey)) || []; 675 | $scope.newItem = {text: ''}; 676 | 677 | $scope.moveItem = function(item, fromIndex, toIndex) { 678 | item.reordered = true; 679 | $scope.searches.splice(fromIndex, 1); 680 | $scope.searches.splice(toIndex, 0, item); 681 | 682 | $timeout(function () { 683 | delete item.reordered; 684 | }, 500); 685 | }; 686 | 687 | $scope.deleteItem = function(item) { 688 | var index = $scope.searches.indexOf(item); 689 | $scope.searches.splice(index, 1); 690 | }; 691 | 692 | $scope.addItem = function () { 693 | if ($scope.newItem.text) { 694 | $scope.searches.push({ 695 | text: $scope.newItem.text 696 | }); 697 | $scope.newItem.text = ''; 698 | } 699 | }; 700 | 701 | $scope.closeModal = function () { 702 | $window.localStorage.setItem(searchesKey, angular.toJson($scope.searches)); 703 | $scope.$parent.modal.remove(); 704 | }; 705 | 706 | $scope.itemClicked = function (filterText, $event) { 707 | var isOptionButtonsClosed = !!$event.currentTarget.querySelector('.item-options.invisible'); 708 | 709 | if (isOptionButtonsClosed) { 710 | $scope.closeModal(); 711 | $scope.$parent.hideBackdrop(); 712 | $scope.$parent.data.filterText = filterText; 713 | $scope.$parent.filterItems(filterText); 714 | } else { 715 | $ionicListDelegate.$getByHandle('searches-list').closeOptionButtons(); 716 | } 717 | }; 718 | 719 | }]); 720 | 721 | })(angular); 722 | -------------------------------------------------------------------------------- /app/assets/lib/ionic-filter-bar/release/ionic.filter.bar.min.css: -------------------------------------------------------------------------------- 1 | .filter-bar-backdrop{-webkit-transition:opacity 150ms ease-in-out;transition:opacity 150ms ease-in-out;opacity:0;background-color:rgba(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:#fff}.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:#aaa}.platform-android .filter-bar .filter-bar-light .item-input-wrapper input[type=search]:-ms-input-placeholder{color:#aaa}.platform-android .filter-bar .filter-bar-light .item-input-wrapper input[type=search]::-webkit-input-placeholder{color:#aaa;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:#aaa}.platform-android .filter-bar .filter-bar-stable .item-input-wrapper input[type=search]:-ms-input-placeholder{color:#aaa}.platform-android .filter-bar .filter-bar-stable .item-input-wrapper input[type=search]::-webkit-input-placeholder{color:#aaa;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:#fff}.platform-android .filter-bar .filter-bar-positive .item-input-wrapper input[type=search]:-ms-input-placeholder{color:#fff}.platform-android .filter-bar .filter-bar-positive .item-input-wrapper input[type=search]::-webkit-input-placeholder{color:#fff;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:#fff}.platform-android .filter-bar .filter-bar-calm .item-input-wrapper input[type=search]:-ms-input-placeholder{color:#fff}.platform-android .filter-bar .filter-bar-calm .item-input-wrapper input[type=search]::-webkit-input-placeholder{color:#fff;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:#fff}.platform-android .filter-bar .filter-bar-assertive .item-input-wrapper input[type=search]:-ms-input-placeholder{color:#fff}.platform-android .filter-bar .filter-bar-assertive .item-input-wrapper input[type=search]::-webkit-input-placeholder{color:#fff;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:#fff}.platform-android .filter-bar .filter-bar-balanced .item-input-wrapper input[type=search]:-ms-input-placeholder{color:#fff}.platform-android .filter-bar .filter-bar-balanced .item-input-wrapper input[type=search]::-webkit-input-placeholder{color:#fff;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:#fff}.platform-android .filter-bar .filter-bar-energized .item-input-wrapper input[type=search]:-ms-input-placeholder{color:#fff}.platform-android .filter-bar .filter-bar-energized .item-input-wrapper input[type=search]::-webkit-input-placeholder{color:#fff;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:#fff}.platform-android .filter-bar .filter-bar-royal .item-input-wrapper input[type=search]:-ms-input-placeholder{color:#fff}.platform-android .filter-bar .filter-bar-royal .item-input-wrapper input[type=search]::-webkit-input-placeholder{color:#fff;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:#444}.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:#fff}.platform-android .filter-bar .filter-bar-dark .item-input-wrapper input[type=search]:-ms-input-placeholder{color:#fff}.platform-android .filter-bar .filter-bar-dark .item-input-wrapper input[type=search]::-webkit-input-placeholder{color:#fff;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:#fff}.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:#aaa}.platform-android .filter-bar .filter-bar-default .item-input-wrapper input[type=search]:-ms-input-placeholder{color:#aaa}.platform-android .filter-bar .filter-bar-default .item-input-wrapper input[type=search]::-webkit-input-placeholder{color:#aaa;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} 2 | -------------------------------------------------------------------------------- /app/assets/lib/ionic-filter-bar/release/ionic.filter.bar.min.js: -------------------------------------------------------------------------------- 1 | module.exports=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); -------------------------------------------------------------------------------- /app/assets/lib/pdfjs-dist/web/viewer.html: -------------------------------------------------------------------------------- 1 |  2 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | PDF.js viewer 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 |
52 | 53 |
54 |
55 |
56 | 59 | 62 | 65 |
66 |
67 |
68 |
69 |
70 | 72 | 74 |
75 |
76 | 77 |
78 | 97 | 98 | 151 | 152 |
153 |
154 |
155 |
156 | 159 |
160 | 163 |
164 | 167 |
168 | 171 |
172 | 173 | 174 | 175 |
176 |
177 | 180 | 181 | 184 | 185 | 188 | 189 | 192 | 193 | Current View 194 | 195 | 196 |
197 | 198 | 201 |
202 |
203 |
204 |
205 | 208 |
209 | 212 |
213 | 214 | 229 | 230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 | 242 | 243 | 245 | 247 | 249 | 251 | 252 | 253 |
254 |
255 |
256 | 257 | 275 |
276 | 277 | 339 | 340 |
341 |
342 | 421 | 422 | 423 | 424 | 425 | -------------------------------------------------------------------------------- /app/assets/scss/_index.scss: -------------------------------------------------------------------------------- 1 | // body { 2 | // background: #efefef; 3 | // } 4 | .border_none { 5 | border: none 6 | } 7 | .border_bottom { 8 | border-bottom: 1px solid #ccc; 9 | } 10 | .border_left { 11 | border-left: 1px solid #ddd; 12 | } 13 | .border_radius { 14 | border-radius: 0px; 15 | } 16 | // //.border_left_none { 17 | // border-right: none; 18 | // } 19 | .width100 { 20 | width: 100%; 21 | } 22 | .font-default { 23 | font-size: 16px; 24 | } 25 | .font_18 { 26 | font-size: 18px; 27 | } 28 | .color_red { 29 | color: #ff5858; 30 | } 31 | .color_white { 32 | color: #fff; 33 | } 34 | .color_blue { 35 | color: #17bddf 36 | } 37 | .float_none { 38 | float: none; 39 | } 40 | .background_red { 41 | background: #ff5858; 42 | } 43 | .background_blue { 44 | background: #17bddf; 45 | } 46 | .background_white { 47 | background: #fff; 48 | } 49 | .margin_auto { 50 | margin: auto; 51 | } 52 | .margin_top { 53 | margin-top: 10px; 54 | } 55 | .margin-top_6 { 56 | margin-top: 6px; 57 | } 58 | .margin_bottom_20 { 59 | margin-bottom: 20px; 60 | } 61 | .margin_bottom_59 { 62 | margin-bottom: 59px; 63 | } 64 | 65 | /**********登录界面的样式 开始 66 | .login_body{ 67 | @extend .font-default; 68 | width: 80%; 69 | margin:auto; 70 | .top_im{ 71 | width:60%; 72 | margin:2.5em auto; 73 | img{ 74 | @extend .width100; 75 | } 76 | } 77 | .input_detail{ 78 | input{ 79 | @extend .border_radius; 80 | border-bottom:1px solid #ddd; 81 | padding: 28px 4px; 82 | } 83 | .code{ 84 | @extend .border_bottom; 85 | input{ 86 | @extend .float_none; 87 | @extend .border_none; 88 | display: inline-block; 89 | } 90 | button{ 91 | @extend .float_none; 92 | @extend .border_none; 93 | @extend .color_red; 94 | @extend .border_radius; 95 | @extend .background_white; 96 | margin-top:2px; 97 | border-left:1px solid #dddddd !important; 98 | display: inline-block; 99 | } 100 | } 101 | } 102 | .login_btn{ 103 | @extend .background_blue; 104 | @extend .border_none; 105 | @extend .border_radius; 106 | color:#fff; 107 | letter-spacing: 3px; 108 | margin-top:3em; 109 | //padding:12px 18px; 110 | } 111 | } 112 | //******************登录界面的样式 结束 113 | 114 | //******************badge 样式 115 | .sub_title{ 116 | margin-top:8px; 117 | position: relative; 118 | font-size:17px; 119 | } 120 | .num{ 121 | @extend .background_red; 122 | position: absolute; 123 | font-size:15px; 124 | border-radius:50%; 125 | color:#fff; 126 | width:21px; 127 | height: 21px; 128 | top:-9px; 129 | } 130 | //******************* 131 | 132 | //*************** 登录页标题样式 133 | .body_bar{ 134 | @extend .border_bottom; 135 | @extend .font-default; 136 | @extend .background_white; 137 | padding:12px 6px 2px; 138 | .back{ 139 | padding: 0 0.8em; 140 | } 141 | .top_title{ 142 | font-size:18px; 143 | margin-top:-28px; 144 | } 145 | } 146 | //********************* 147 | 148 | //间隔样式 149 | .gap{height:20px;background:#efefef;} 150 | //间隔样式 151 | 152 | //**************** 配送端标题样式 153 | .delivery_bar{ 154 | @extend .font-default; 155 | @extend .background_white; 156 | padding:13px 6px 2px; 157 | position:fixed; 158 | width:100%; 159 | top:0; 160 | z-index: 9999; 161 | border-top:1px solid #ddd; 162 | .top_title1{ 163 | font-size:18px; 164 | margin-top:-26px; 165 | width:65%; 166 | position: relative; 167 | display:inline-block; 168 | } 169 | .badge{ 170 | @extend .background_red; 171 | @extend .color_white; 172 | position: absolute; 173 | top:-7px; 174 | } 175 | .search{ 176 | margin-top:-2.4em; 177 | padding: 0 0.8em; 178 | img{ 179 | width:1.5em; 180 | } 181 | } 182 | .btn_search{ 183 | margin-top:-8px; 184 | float: right; 185 | img{ 186 | width:1.5em; 187 | } 188 | } 189 | } 190 | 191 | .back_arrow{ 192 | padding: 0.8em 1.5em 0.8em 0.8em; 193 | img{ 194 | width:0.6em; 195 | } 196 | } 197 | //**************** 198 | 199 | //配送端选择标题样式 200 | .choose_bar{ 201 | @extend .background_white; 202 | text-align: center; 203 | padding:1em 0; 204 | div{ 205 | display:inline-block; 206 | margin-left:2.4em; 207 | img{ 208 | width:1em; 209 | margin-left:0.8em; 210 | } 211 | } 212 | } 213 | //配送端选择标题样式 214 | 215 | //*********角标样式 216 | .img_mark{ 217 | width: 30%; 218 | position: absolute; 219 | top:-6px; 220 | } 221 | .img_mark_title{ 222 | @extend .color_white; 223 | position: absolute; 224 | top:-5px; 225 | left:10px; 226 | letter-spacing: 3px; 227 | } 228 | //*********角标样式 229 | 230 | //********放格内容样式 231 | .lists_details { 232 | @extend .border_left; 233 | padding:25px 0; 234 | border:1px solid #ccc; 235 | div img{ 236 | width:2.5em; 237 | margin-top:12px; 238 | } 239 | } 240 | //********方格内容样式 241 | 242 | .default{ 243 | position: relative; 244 | @extend .margin_bottom_20; 245 | .body_lists{ 246 | @extend .margin_auto; 247 | @extend .background_white; 248 | } 249 | } 250 | .water{ 251 | position: relative; 252 | @extend .background_white; 253 | @extend .margin_bottom_20; 254 | } 255 | .wash{ 256 | position:relative; 257 | @extend .background_white; 258 | @extend .margin_bottom_20; 259 | } 260 | //************************ 261 | 262 | //首页底部菜单选择样式 263 | .tabs-red > .tabs, 264 | .tabs.tabs-red { 265 | border-color: #fff; 266 | background-color: #fff; 267 | background-image: linear-gradient(0deg, #fff, #fff 50%, transparent 50%); 268 | color: #ff5858; } 269 | .tabs-red > .tabs .tab-item .badge, 270 | .tabs.tabs-red .tab-item .badge { 271 | background-color: #fff; 272 | color: #ff5858; } 273 | .tabs-striped.tabs-red .tabs { 274 | background-color: #fff; } 275 | 276 | .tabs-striped.tabs-red .tab-item { 277 | color: rgba(0, 0, 0, 0.6); 278 | opacity: 1; } 279 | .tabs-striped.tabs-red .tab-item .badge { 280 | opacity: 0.4; } 281 | .tabs-striped.tabs-red .tab-item.tab-item-active, .tabs-striped.tabs-assertive .tab-item.active, .tabs-striped.tabs-assertive .tab-item.activated { 282 | margin-top: -2px; 283 | color: #ff5858; 284 | border-style: solid; 285 | border-width: 4px 0 0 0; 286 | border-color: #ff5858; } 287 | 288 | 289 | /*公用样式*/ 290 | .txt-center { 291 | text-align: center; 292 | } 293 | .clear { 294 | clear: both; 295 | } 296 | .button.button-block2 { 297 | margin-top: 0px; 298 | margin-bottom: 0px; 299 | font-size: 14px; 300 | } 301 | 302 | /*定位*/ 303 | .position-ab { 304 | position: absolute; 305 | } 306 | .position-rt { 307 | position: relative; 308 | } 309 | 310 | /*浮动*/ 311 | .float-lt { 312 | float: left; 313 | } 314 | .float-rt { 315 | float: right; 316 | } 317 | 318 | /*宽度*/ 319 | .width15 { 320 | width: 15px; 321 | } 322 | .width35 { 323 | 324 | /* width: 35%;*/ 325 | width: 60px; 326 | } 327 | .width60 { 328 | width: 60%; 329 | } 330 | .width100 { 331 | width: 100%; 332 | } 333 | .width125 { 334 | width: 125px; 335 | } 336 | 337 | /* 高度 */ 338 | .height60 { 339 | height: 60px; 340 | line-height: 60px; 341 | } 342 | .height44 { 343 | height: 44px; 344 | line-height: 44px; 345 | } 346 | .height10 { 347 | height: 10px; 348 | } 349 | .height1 { 350 | height: 2px; 351 | } 352 | 353 | /* 行高 */ 354 | .line-height50 { 355 | margin: 0; 356 | line-height: 50px; 357 | } 358 | 359 | /*间距*/ 360 | .margin25 { 361 | margin: 0 25px; 362 | } 363 | .margin-lt5 { 364 | margin-left: 5px; 365 | } 366 | .margin-lt6 { 367 | margin-left: 6%; 368 | } 369 | .margin-lt13 { 370 | margin-left: 13px; 371 | } 372 | .margin-lt15 { 373 | margin-left: 15px; 374 | } 375 | .margin-lt20 { 376 | margin-left: 20px; 377 | } 378 | .margin-lt40 { 379 | margin-left: 40px; 380 | } 381 | .margin-lt60 { 382 | margin-left: 60px; 383 | } 384 | .margin-rt5 { 385 | margin-right: 5px; 386 | } 387 | .margin-rt10 { 388 | margin-right: 10%; 389 | } 390 | .margin-rt10px { 391 | margin-right: 10px; 392 | } 393 | .margin-rt40 { 394 | margin-right: 40px; 395 | } 396 | .margin-top4 { 397 | margin-top: 4px; 398 | } 399 | .margin-top2 { 400 | margin-top: 2px; 401 | } 402 | .margin-top5 { 403 | margin-top: 5px; 404 | } 405 | .margin-top10 { 406 | margin-top: 10px; 407 | } 408 | .margin-top-10 { 409 | margin-top: 10%; 410 | } 411 | .margin-top70 { 412 | margin-top: 70px; 413 | z-index: 66; 414 | } 415 | .margin-bt10 { 416 | margin-bottom: 10px; 417 | } 418 | .margin-bt44 { 419 | margin-bottom: 44px; 420 | } 421 | .padding-lt10 { 422 | padding-left: 10px; 423 | } 424 | .item .padding-lt10 { 425 | padding-left: 10px; 426 | } 427 | .padding-rt10 { 428 | padding-right: 10px; 429 | } 430 | .padding5 { 431 | padding-top: 5px; 432 | padding-bottom: 5px; 433 | } 434 | .padding-bt20 { 435 | padding-bottom: 20px; 436 | } 437 | .paddingS { 438 | padding-top: 10px; 439 | padding-bottom: 5px; 440 | } 441 | .padding10 { 442 | padding: 10px 16px; 443 | } 444 | .padding15 { 445 | padding: 0px 15px; 446 | } 447 | .padding25 { 448 | padding: 0px 25px; 449 | } 450 | .item-button-right > .top4 { 451 | top: 4px; 452 | } 453 | 454 | /*字体大小*/ 455 | .font-size16 { 456 | font-size: 16px; 457 | } 458 | #font-size14 { 459 | font-size: 14px; 460 | } 461 | .bar .font-size16 { 462 | font-size: 16px; 463 | } 464 | .font-size10 { 465 | font-size: 10px; 466 | } 467 | 468 | /*顶2格*/ 469 | .text-indent2 { 470 | text-indent: 2em; 471 | } 472 | 473 | /*字体颜色*/ 474 | .button-active { 475 | color: #17bddf; 476 | } 477 | .color-gray { 478 | color: #626262; 479 | } 480 | .color-gray2 { 481 | color: #aaa; 482 | } 483 | .color-blue { 484 | color: #17bddf; 485 | } 486 | 487 | /*主题色*/ 488 | 489 | /*列表样式*/ 490 | .item-h2 { 491 | vertical-align: middle; 492 | line-height: 50px; 493 | } 494 | .item-avatar > img:first-child { 495 | max-width: 50px; 496 | max-height: 50px; 497 | } 498 | .item-avatar .item-content > img:first-child { 499 | max-width: 50px; 500 | max-height: 50px; 501 | } 502 | 503 | /*背景色*/ 504 | .bg-gray2 { 505 | background: #f0f0f0; 506 | } 507 | .bg-gray3 { 508 | background: #f8f8f8; 509 | } 510 | .bg-white { 511 | background-color: #fff; 512 | } 513 | .bg-blue { 514 | background-color: #00abec; 515 | } 516 | 517 | /*滚动条样式*/ 518 | .ion-scroll-style { 519 | width: 100%; 520 | height: 4em; 521 | white-space: nowrap; 522 | } 523 | .ion-scroll-div { 524 | width: 100.5%; 525 | height: 4em; 526 | cursor: pointer; 527 | } 528 | .ion-scroll-box { 529 | overflow: hidden; 530 | margin: 0; 531 | padding: 0; 532 | line-height: 44px; 533 | vertical-align: middle; 534 | } 535 | .bar .btn-scroll { 536 | background: none; 537 | border: none; 538 | font-size: 16px; 539 | vertical-align: middle; 540 | color: #626262; 541 | } 542 | .bar .color-blue { 543 | color: #17bddf; 544 | } 545 | 546 | /*描边*/ 547 | .border-top { 548 | border-top: #ddd 1px solid; 549 | overflow: hidden; 550 | } 551 | .border-top2 { 552 | border-top: 1px #dcdcdc solid; 553 | } 554 | .border-lt5 { 555 | border-left: 3px solid #17bddf; 556 | padding-left: 10px; 557 | } 558 | /*我的工作待随访*/ 559 | .border-lt { 560 | border-left: 1px solid #b4b4b4; 561 | height: 100%; 562 | position: absolute; 563 | top: 0; 564 | left: 50px; 565 | z-index: 2; 566 | overflow: hidden; 567 | } 568 | .border-top-none { 569 | border-top: none; 570 | } 571 | .border-bt-none { 572 | border-bottom: none; 573 | } 574 | 575 | /*按钮*/ 576 | .button2 { 577 | border-color: #626262; 578 | background-color: #f8f8f8; 579 | color: #626262; 580 | position: relative; 581 | display: inline-block; 582 | margin: 0; 583 | padding: 0 12px; 584 | min-width: 52px; 585 | min-height: 30px; 586 | border-width: 1px; 587 | border-style: solid; 588 | border-radius: 4px; 589 | vertical-align: top; 590 | text-align: center; 591 | text-overflow: ellipsis; 592 | font-size: 16px; 593 | line-height: 30px; 594 | cursor: pointer; 595 | } 596 | .bar .button3 { 597 | font-size: 16px; 598 | } 599 | .bar .button3:hover, .button3:hover { 600 | color: #17bddf; 601 | } 602 | .button4 { 603 | min-height: 34px; 604 | line-height: 34px; 605 | margin: 5px 20px; 606 | } 607 | .button5 { 608 | min-height: 34px; 609 | line-height: 34px; 610 | margin: 5px 10px; 611 | } 612 | .button-phone { 613 | border: none; 614 | background: url(../images/phone_icon.png) no-repeat; 615 | background-size: 100%; 616 | min-width: 25px; 617 | min-height: 25px; 618 | } 619 | .button-edit { 620 | border: none; 621 | background: url(../images/icon_edit.png) no-repeat; 622 | background-size: 100%; 623 | min-width: 25px; 624 | min-height: 25px; } 625 | .button-tb { 626 | border: none; 627 | background: url(../images/refresh.png) no-repeat; 628 | background-size: 100%; 629 | min-width: 25px; 630 | min-height: 25px; 631 | } 632 | .button-add { 633 | border: none; 634 | background: url(../images/icon_add.png) no-repeat; 635 | background-size: 100%; 636 | min-width: 25px; 637 | min-height: 25px; } 638 | .arrow-down-button, .arrow-up-button { 639 | background-size: 100%; 640 | width: 30px; 641 | height: 30px; 642 | margin: auto; 643 | position: absolute; 644 | left: 50%; 645 | margin-left: -15px; 646 | bottom: 5px; 647 | } 648 | .arrow-down-button { 649 | background: url(../images/list_arrow_down.png) no-repeat; 650 | background-size: 100%; 651 | } 652 | .arrow-up-button { 653 | background: url(../images/list_arrow_up.png) no-repeat; 654 | background-size: 100%; 655 | } 656 | .add-img { 657 | width: 25px; 658 | margin-top: -8px; 659 | margin-left: 10px; 660 | } 661 | 662 | /*我的工作*/ 663 | .timer { 664 | position: absolute; 665 | left: 5px; 666 | z-index: 3; 667 | line-height: 82px; 668 | } 669 | .timer-img { 670 | position: absolute; 671 | left: 45px; 672 | top: 0; 673 | z-index: 3; 674 | } 675 | .item2 { 676 | padding-left: 110px; 677 | border-color: #fff; 678 | } 679 | 680 | /*弹出框背景*/ 681 | .popup-box-bg { 682 | position: absolute; 683 | left: 0; 684 | top: 0; 685 | background-color: #000; 686 | opacity: 0.5; 687 | height: 100%; 688 | width: 100%; 689 | } 690 | 691 | /* 未创建家庭信息提示框 */ 692 | .prompt-box { 693 | background: url(../images/cjjt_bg.png) no-repeat; 694 | background-size: 320px; 695 | position: absolute; 696 | left: 0; 697 | top: 170px; 698 | left: 50%; 699 | margin-left: -160px; 700 | z-index: 66; 701 | width: 100%; 702 | height: 100%; 703 | } 704 | 705 | /* 确定弹出框 */ 706 | .popup-box { 707 | width: 251px; 708 | height: auto; 709 | background-color: #fff; 710 | position: fixed; 711 | margin: -60px auto auto -125px; 712 | top: 50%; 713 | left: 50%; 714 | border-radius: 4px; 715 | z-index: 66; 716 | } 717 | .popup-button { 718 | border: none; 719 | background-color: #fff; 720 | height: 45px; 721 | width: 125px; 722 | color: #17bddf; 723 | float: left; 724 | } 725 | .popup-button:hover { 726 | background-color: #dcdcdc; 727 | } 728 | .popup-radius-lt { 729 | border-bottom-left-radius: 4px; 730 | border-right: 1px #dcdcdc solid; 731 | } 732 | 733 | /*就诊信息*/ 734 | .item-input2 { 735 | padding: 6px 0 5px 0px; 736 | font-size: 16px; 737 | } 738 | .item-padding { 739 | padding: 6px 16px 5px 16px; 740 | } 741 | 742 | /*底部*/ 743 | .footer-div { 744 | background: #fff; 745 | line-height: 44px; 746 | text-align: center; 747 | position: absolute; 748 | left: 0; 749 | bottom: 0; 750 | width: 100%; 751 | } 752 | 753 | /*我的工作——用药情况*/ 754 | .footer-btn3 li { 755 | width: 30%; 756 | display: inline-block; 757 | } 758 | 759 | /* 副标题 */ 760 | .bar-subheader2 { 761 | top: 44px; 762 | display: block; 763 | height: auto; 764 | margin: 0; 765 | padding: 0; 766 | } 767 | .has-subheader2 { 768 | top: 180px; 769 | } 770 | .has-subheader3 { 771 | top: 141px; 772 | } 773 | .has-subheader4 { 774 | top: 223px; 775 | } 776 | .has-subheader5 { 777 | top: 200px; 778 | } 779 | /*不良生活习惯*/ 780 | .ul-box li { 781 | float: left; 782 | width: 30%; 783 | } 784 | .popup-radius-rt { 785 | border-bottom-right-radius: 4px; 786 | } -------------------------------------------------------------------------------- /app/assets/scss/_mainInterface.scss: -------------------------------------------------------------------------------- 1 | .bg-gray{background:#959595} 2 | #three_icons{ 3 | div img{ 4 | margin:0 auto; 5 | } 6 | div p{ 7 | text-align:center; 8 | } 9 | } 10 | #subheader_button{ 11 | text-align: center; 12 | button{ 13 | margin:0 1.2em;} 14 | } -------------------------------------------------------------------------------- /app/assets/scss/main.scss: -------------------------------------------------------------------------------- 1 | @import 2 | "index", 3 | "mainInterface" 4 | ; -------------------------------------------------------------------------------- /app/config.route.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = routeConfig ; 3 | 4 | 5 | function routeConfig($stateProvider, $urlRouterProvider) { 6 | 7 | var routes, setRoutes; 8 | 9 | routes = [ { 10 | name: 'root', 11 | ctrl: 'mainCtrl', 12 | url: 'main', 13 | tpl: './modules/business/main/main' 14 | } 15 | ]; 16 | setRoutes = function (route) { 17 | 18 | var config, name; 19 | config = { 20 | url: "/" + route.url, 21 | template: require(route.tpl + '.html'), 22 | controller: route.ctrl 23 | }; 24 | $stateProvider.state(route.name, config); 25 | return $stateProvider; 26 | }; 27 | routes.forEach(function (route) { 28 | return setRoutes(route); 29 | }); 30 | 31 | } 32 | 33 | -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | angular = require('angular'); 2 | var ionic = require('./assets/lib/ionic/release/js/ionic.bundle.min'); 3 | const bussiness = require('./modules/business'); 4 | require('./assets/styles/ionic.min.css'); 5 | require('./assets/lib/calendar-pk/release/css/calendar_pk.min.css'); 6 | require('./assets/lib/ionic-filter-bar/release/ionic.filter.bar.min.css'); 7 | ngCache = require('angular-cache'); 8 | ngCookies = require('angular-cookies'); 9 | angular.module("app",[ionic,bussiness.name ,'calendar_pk',ngCookies,ngCache]) 10 | .config(['$stateProvider', '$urlRouterProvider', require('./config.route')]) 11 | .controller("AppCtrl", require('./app.controller')); 12 | const appConfig = require('./app.config'); -------------------------------------------------------------------------------- /app/modules/business/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../../assets/lib/ionic-filter-bar/release/ionic.filter.bar.min.css'); 3 | var filterBar = require('../../assets/lib/ionic-filter-bar/release/ionic.filter.bar.min'); 4 | 5 | module.exports = angular.module("app.business", 6 | [filterBar.name,'ionic-datepicker']); 7 | 8 | 9 | //把所有js文件引入 10 | function importAll (r) { 11 | r.keys().forEach(r); 12 | } 13 | importAll(require.context('./', true, /\.js$/)); 14 | -------------------------------------------------------------------------------- /app/modules/business/main/main.controller.js: -------------------------------------------------------------------------------- 1 | module.exports = angular.module('app.business') 2 | .controller('mainCtrl', ['$scope', '$http', '$stateParams', '$state', '$rootScope', '$filter', appointmentManagementCtrl]) 3 | 4 | 5 | function appointmentManagementCtrl($scope, $http, $stateParams, $state, $rootScope, $filter) { 6 | 7 | $scope.eventSource = []; 8 | $scope.currentMonth = new Date().getMonth()+1; //日历控件参数 9 | $scope.morning = ['9:00', '9:30', '10:00', '10:30', '11:00', '11:30', '12:00']; 10 | $scope.afternoon = ['2:00', '2:30', '3:00', '3:30', "4:00", "4:30", '5:00', '5:30']; 11 | $scope.evening = ['7:00', '7:30', '8:00', '8:30', '9:00', '9:30', '10:00']; 12 | $scope.appointmentNumList = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15']; 13 | 14 | $scope.onTimeSelected = function (selectedTime) { 15 | $scope.currentDate = $filter('date')(selectedTime, 'yyyy-MM-dd'); 16 | loadData($scope.currentDate); 17 | }; 18 | 19 | $scope.onMonthChanged = function (startTime, endTime, display) { 20 | $scope.currentMonth = display; 21 | loadEvents(); 22 | console.log('Changed month : ' + display); 23 | }; 24 | 25 | } 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/modules/business/main/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 |
9 |

angular-ionic-on-webpack

10 |
11 | 12 |
13 |
14 |
15 | 如果你看到这个页面,说明项目成功运行了 16 |
17 |
18 |
19 |
20 |
21 |
22 | -------------------------------------------------------------------------------- /build/configDevServer.js: -------------------------------------------------------------------------------- 1 | const server={ 2 | contentBase:'/dist/', 3 | host: 'localhost', 4 | port: 8089, 5 | inline: true, // 可以监控js变化 6 | hot: true, // 热启动 7 | compress: true, 8 | watchContentBase: true, 9 | proxy: {//设置代理服务器,用于调试接口 10 | '/api':{ 11 | target:'http://www.baidu.com', 12 | pathRewrite:{"^/api": "/api"} 13 | } 14 | } 15 | }; 16 | module.exports= server; 17 | -------------------------------------------------------------------------------- /build/webpack.base.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var root = path.resolve(__dirname, '../'); 3 | 4 | module.exports = { 5 | entry: { 6 | 'main': root + '\\app\\index.js', 7 | jquery:['jquery'], 8 | ionic:root+'\\app\\assets\\lib\\ionic\\release\\js\\ionic.bundle.min.js', 9 | datepicker:root+'\\app\\assets\\lib\\ionic-datepicker\\release\\ionic-datepicker.bundle.min.js', 10 | calendar_pk:root+'\\app\\assets\\lib\\calendar-pk\\release\\js\\calendar_pk.min.js' 11 | }, 12 | module: { 13 | rules: [ 14 | { 15 | test: /\.(png|jpe?g|gif|woff|svg|eot|ttf)(\?.*)?$/, 16 | loader: 'url-loader', 17 | query: { 18 | limit: 10000, 19 | } 20 | }, 21 | { 22 | test: /\.html$/, 23 | loader: 'html-loader' 24 | } 25 | 26 | ] 27 | }, 28 | resolve: { 29 | extensions: ['.js', '.json'] 30 | } 31 | 32 | }; 33 | -------------------------------------------------------------------------------- /build/webpack.dev.config.js: -------------------------------------------------------------------------------- 1 | var baseconf = require('./webpack.base.config'); 2 | var merge = require('webpack-merge'); 3 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | var webpack = require('webpack'); 5 | var server = require('./configDevServer'); 6 | var path = require('path'); 7 | var root = path.resolve(__dirname, '../'); 8 | 9 | var plugins = [ 10 | new webpack.DefinePlugin({ 11 | 'process.env': { 12 | NODE_ENV: JSON.stringify("development") 13 | } 14 | }), 15 | new webpack.ProvidePlugin({ 16 | $: 'jquery', 17 | jQuery: 'jquery', 18 | 'window.jQuery': 'jquery', 19 | 'window.$': 'jquery' 20 | }), 21 | new webpack.optimize.CommonsChunkPlugin({ 22 | name: 'vendor', // 这公共代码的chunk名为'commons' 23 | filename: '[name].bundle.js', // 生成后的文件名,虽说用了[name],但实际上就是'commons.bundle.js'了 24 | minChunks: 3, // 设定要有4个chunk(即4个页面)加载的js模块才会被纳入公共代码。这数目自己考虑吧,我认为3-5比较合适。 25 | }), 26 | new HtmlWebpackPlugin({ 27 | filename: 'index.html', 28 | template: 'index.html', 29 | inject: true 30 | }), 31 | new webpack.HotModuleReplacementPlugin() 32 | ]; 33 | baseconf.module.rules.push( 34 | { 35 | test: /\.css$/, 36 | loader: ['style-loader','css-loader'], 37 | } 38 | ); 39 | module.exports = merge(baseconf, { 40 | output: { 41 | path: root+"/dist", 42 | publicPath: "/", 43 | filename: "./js/[name].[chunkhash].js" 44 | }, 45 | devtool: 'cheap-module-eval-source-map', 46 | devServer: server, 47 | plugins: plugins, 48 | }); 49 | -------------------------------------------------------------------------------- /build/webpack.prod.config.js: -------------------------------------------------------------------------------- 1 | var baseconf = require('./webpack.base.config'); 2 | var path = require('path'); 3 | var root = path.resolve(__dirname, '../'); 4 | var merge = require('webpack-merge'); 5 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 6 | var webpack = require('webpack'); 7 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 8 | var ManifestPlugin = require('webpack-manifest-plugin'); 9 | var plugins = [ 10 | new webpack.DefinePlugin({ 11 | 'process.env': { 12 | NODE_ENV: JSON.stringify("development") 13 | } 14 | }), 15 | new webpack.optimize.UglifyJsPlugin({ 16 | compress: { 17 | warnings: false 18 | } 19 | }), 20 | new HtmlWebpackPlugin({ 21 | filename: 'index.html', 22 | template: 'index.html', 23 | inject: true 24 | }), 25 | new ExtractTextPlugin({ 26 | filename: './[name].css?[contenthash:8]', 27 | allChunks: true, 28 | }), 29 | new webpack.ProvidePlugin({ 30 | $: 'jquery', 31 | jQuery: 'jquery', 32 | 'window.jQuery': 'jquery', 33 | 'window.$': 'jquery', 34 | }), 35 | new webpack.optimize.CommonsChunkPlugin({ 36 | name: 'commons', // 这公共代码的chunk名为'commons' 37 | filename: './js/[name].bundle.js', // 生成后的文件名,虽说用了[name],但实际上就是'commons.bundle.js'了 38 | minChunks: 3, // 设定要有4个chunk(即4个页面)加载的js模块才会被纳入公共代码。这数目自己考虑吧,我认为3-5比较合适。 39 | }), 40 | new ManifestPlugin(path.join('dist', 'manifest.json')) 41 | ]; 42 | baseconf.module.rules.push( 43 | { 44 | test: /\.css$/, 45 | use: ExtractTextPlugin.extract({ 46 | fallback: "style-loader", 47 | use: "css-loader" 48 | }) 49 | } 50 | ); 51 | module.exports = merge(baseconf, { 52 | output: { 53 | path: root + "/dist", 54 | publicPath: "./", 55 | filename: "./js/[name].[chunkhash].js" 56 | }, 57 | devtool: false, 58 | plugins: plugins 59 | }); 60 | -------------------------------------------------------------------------------- /docs/angular-m-part.md: -------------------------------------------------------------------------------- 1 | # 基于webpack构建的angular 1.x工程(angular篇) 2 | 3 |   上一篇[基于webpack构建的angular 1.x 工程(一)webpack篇](./webpack-part.md)里我们已经成功构建了整个项目的打包配置。 4 | 接下来我们要继续让angular在工程里跑起来。 5 | ## 首先要知道怎么改写 6 | 之前的工程由于是用gulp打包的,具体原理我不太懂,不过貌似会把所有的js自动注入到index.html中。 7 | 由于js很多,所以为了不互相干扰,产生全局变量污染的问题,它里面所有angular都是用立即执行函数表达式(IIFE)来写的: 8 | ``` 9 | (function(){ 10 | 'use strict'; 11 | 12 | angular.module("app.core",[ 13 | 'ngCookies', 14 | 'angular-cache' 15 | ]); 16 | })(); 17 | ``` 18 |   这样的写法在webpack是不必要的了,webpack是根据js之间的依赖关系来加载打包项目的。 19 | 不同的模块之间webpack都会有标识来标志,所以不会说存在干扰和污染的问题。那我们应该怎么写呢? 20 | 要写成AMD/CMD规范形式的。为了方便理解,我们把立即执行函数表达式去掉,改成这样的: 21 | ``` 22 | const ngCookies = require('angular-cookies') 23 | const ngCache = require('angular-cache') 24 | module.exports = angular.module("app.core",[ 25 | ngCookies, 26 | ngCache 27 | ]); 28 | ``` 29 |   这个是符合webpack要求的写法。首先先引入我们需要的模块,然后编写我们的模块,最后输出我们要暴露给外部调用的接口。 30 | 于是我就把所有IIFE都改成了这种形式。 31 | ## controller那些要怎么办? 32 | 接下来问题就来了,在同一个angular应用模块(module)中,各个控制器(controller)、过滤器(filters)、服务(services)等之间都是并列的兄弟关系,都是从属于模块。 33 | 那我们应该来处理这些关系呢? 34 | 经过查阅过别人的项目之后,我发现其实有两种写法: 35 | 1. 把各个从属的具体方法都写成一个模块,然后在模块声明时进行引入并声明,就像这样: 36 | >main.controller.js 37 | ``` 38 | module.exports =function mainCtrl($scope, $http, $stateParams, $state, $rootScope, $filter) { 39 | // your controller goes here 40 | } 41 | ``` 42 | >index.js 43 | ``` 44 | angular.module("app",[]) 45 | .controller("mainCtrl", [$scope, $http, $stateParams, $state, $rootScope, $filter,require('./main.controller')]); 46 | ``` 47 |   这样的其实也可以输出一个数组,像这样: 48 | >main.controller.js 49 | ``` 50 | module.exports =[[$scope, $http, $stateParams, $state, $rootScope, $filter, function mainCtrl($scope, $http, $stateParams, $state, $rootScope, $filter) { 51 | // your controller goes here 52 | }] 53 | ``` 54 |   相对应的,主要入口要这样写: 55 | >index.js 56 | ``` 57 | angular.module("app",[]) 58 | .controller("mainCtrl", require('./main.controller')); 59 | ``` 60 |   这样的写法适合从头开始的项目,好处是分的比较清晰。 61 | 但是对于我这个重构的项目,就会有麻烦:要改写的文件有太多了。 62 | 这么麻烦,我只能抛弃这种方式。 63 | 64 | 2. 每个模块都直接输出的是模块声明,然后只要把这个文件引入即可。 65 |   熟悉angular的都知道,angular在整个应用中其实一个全局定义的对象。 66 | 每个模块在angular里注册之后,都会在angular里找得到。 67 | 这样的话,只要确保运行下面这段代码即可: 68 | ``` 69 | angular.module("app") 70 | .controller("mainCtrl", [$scope,mainCtrl($scope){ 71 | // your controller goes here 72 | }]); 73 | ``` 74 |   也就是说,我只要引用了这段代码,也算把这段代码运行了。 75 | 那这样的我就可以这样写: 76 | >main.controller.js 77 | ``` 78 | module.exports = angular.module("app") 79 | .controller("mainCtrl", [$scope,mainCtrl($scope){ 80 | // your controller goes here 81 | }]); 82 | ``` 83 | >index.js 84 | ``` 85 | angular.module("app",[]) 86 | require('./main.controller') 87 | ``` 88 |   在`main.controller.js`我直接输出的是angular声明app模块的controller,然后在`index.js`定义模块之后,把这个文件引入之后,就相当于同时声明了这个controller,免去大量改动代码的麻烦。 89 | 不过另一个问题出现了:我这里虽然免去了大量改动代码的麻烦,但是我那么多的controller,真的要一一写路径来引用吗?这样还是麻烦啊。 90 | 不要惊慌。webpack已经预想到你这有这个问题了,特意写了一个可以引用大量文件的方法给你:`require.context`。 91 | 这个方法可以让你查询指定路径的指定文件类型,然后引用进来。 92 | 我们这里由于已经分类放好了,所有的controller都放在`/app/module`目录下面,因此查找也是轻而易举的事。所以我们的`index.js`可以写成这样: 93 | ``` 94 | module.exports = angular.module("app",[]); 95 | 96 | //把所有js文件引入 97 | function importAll (r) { 98 | r.keys().forEach(r); 99 | } 100 | importAll(require.context('./', true, /\.js$/)); 101 | 102 | ``` 103 |   这样就解决了那些controller,filters等的问题。具体`require.context`的用法[参考这里]() 104 | ## 模块之间引用的问题 105 |   当我们往我们的模块注入其他模块(自己写的或者angular插件)的时候,这个环节也有些要注意的地方。 106 |   首先,我们知道,angular注入其他模块的时候,其实只需要写注入模块的名字就可以了,angular可以自行去寻找相应的模块。 107 | 这样的话,我们像上面那样写的模块声明,直接输出其实会有问题: 108 | >app.core.module.js 109 | ``` 110 | module.exports = angular.module("app.core",[]) 111 | ``` 112 |   这里其实输出的是angular的模块,并不是模块的名字。如果我们直接引用的话,像这样: 113 | >index.js 114 | ``` 115 | var appCore = require('./modules/appCore.module.js') 116 | module.exports = angular.module("app",[appCore]); 117 | ``` 118 |   这样的话,angular就会报错: 119 | ``` 120 | Error: [ng:areq] Argument 'module' is not a function, got Object 121 | ``` 122 |   要解决这个问题其实很简单,只要调用angular的`.name`方法就可以了,所以上面可以改写成这样: 123 | >app.core.module.js 124 | ``` 125 | module.exports = angular.module("app.core",[]).name 126 | ``` 127 |   或者这样改: 128 | >index.js 129 | ``` 130 | var appCore = require('./modules/appCore.module.js') 131 | module.exports = angular.module("app",[appCore.name]); 132 | ``` 133 |   两种方法选一个执行即可。 134 | 135 |   其实如果是插件的话,你在`npm`安装的插件一般都不用担心这个问题,毕竟人家早就想到会有这个问题了。 136 | 但是如果是其他途径弄来的话,这个就复杂了。 137 | 138 | ## 插件注入的另一种问题 139 |   上面提到的是插件注入可能会遇到的问题之一。然而还有一种情况。 140 | 这种情况就是插件也使用了IIFE(立即执行函数表达式)。 141 | 听起来就很烦。自己的代码,自己知道怎么写的,所以改起来不会怎么出问题,但是别人的代码的话就不一定了。 142 | 为了避免错误,我选择不改动插件的代码。而是,直接在打包的时候分开打包,然后直接注入的时候写上插件名字即可以注入成功。详细可以看我的webpack配置。 143 | 144 | 以上就是用webpack打包angular 1.x 的时候写angular所需要注意的地方。如果想看webpack的配置可以查看我前一篇文章: 145 | 146 | 147 | [基于webpack构建的angular 1.x 工程(一)webpack篇](./webpack-part.md) 148 | 149 | 用于参考的一位前辈的类似项目,让大家也参考一下: 150 | [https://github.com/IamBusy/webpack-angular](https://github.com/IamBusy/webpack-angular) 151 | 152 | 153 | 想看详细代码,可以访问我的项目地址 154 | [https://github.com/homerious/angular-ionic-webpack](https://github.com/homerious/angular-ionic-webpack) 155 | 156 | 有什么问题或者不对的地方欢迎指出,谢谢阅读! 157 | 158 | 本文原创,未经授权请勿转载。 159 | -------------------------------------------------------------------------------- /docs/webpack-part.md: -------------------------------------------------------------------------------- 1 | # 基于webpack构建的angular 1.x 工程(一)webpack篇 2 | 3 |   现在[Angular](https://angularjs.org/)都已经出到4.x的版本了,可我对它的认识还是停留在Angularjs1.x。 4 |   之前用它是为了搭配[ionic](http://ionicframework.com/docs/)来写[web手机天气 应用](https://github.com/homerious/homerWeather)(用来应付我大学里一门学科的课设的︿( ̄︶ ̄)︿)。之后就因为它太难学而没有继续深入下去。 5 |   现在就职的公司也有个项目是做混合式的手机app的,居然也是用[AngularJS](https://angularjs.org/)+[ionic](http://ionicframework.com/docs/)来做的,而且也是用1.x的版本。 6 |   本来没我什么事的,我这段时间都在用[Vuejs](https://cn.vuejs.org/v2/)。然后上头发现那个项目加载是在太慢了,问我有没有优化的方法。我看了下项目工程结构,发现是用[gulp](https://gulpjs.com/)打包的一个工程。可能刚开始做这个项目的时候没掌握好要点,导致整个项目臃肿不堪。[gulp](https://gulpjs.com/)我是不会的了,由于一直在用[Vuejs](https://cn.vuejs.org/v2/),官方cli提供的模板就是用[webpack](http://webpack.github.io/docs/)打包的,而且我之前写[ReactJS](http://react-china.org/)用的也是[webpack](http://webpack.github.io/docs/)来打包的。因此,我就用了[webpack](http://webpack.github.io/docs/)来重构一下工程。然后写下这篇详细的文章,想给可能会同样遇到的这种问题的朋友做一个参考( • ̀ω•́ )✧。 7 | 另外,本文也可以当做webpack的一篇入门文章。 8 | #### 首先,要先配置好工程文件。 9 |   我先列一下我的`package.json`里的配置: 10 | ``` 11 | { 12 | "name": "angular-ionic-webpack", 13 | "version": "1.0.0", 14 | "description": "a project base on angular 1.x and webpack", 15 | "main": "index.js", 16 | "scripts": { 17 | "build": "webpack --config ./build/webpack.prod.config.js", 18 | "dev": "set NODE_ENV=dev&& webpack-dev-server --config ./build/webpack.dev.config.js" 19 | }, 20 | "devDependencies": { 21 | "css-loader": "^0.26.4", 22 | "extract-text-webpack-plugin": "^3.0.1", 23 | "html-loader": "^0.4.4", 24 | "html-webpack-plugin": "^2.24.1", 25 | "style-loader": "^0.13.1", 26 | "url-loader": "^0.5.7", 27 | "webpack": "^3.1.0", 28 | "webpack-dev-server": "^2.9.2", 29 | "webpack-manifest-plugin": "^1.3.2", 30 | "webpack-merge": "^4.1.0" 31 | }, 32 | "dependencies": { 33 | "angular": "1.4.3", 34 | "angular-cache": "^4.5.0", 35 | "angular-cookies": "1.4.12", 36 | "angular-ui-router": "^0.3.2", 37 | "jquery": "^3.2.1" 38 | }, 39 | "author": "homer", 40 | "license": "MIT" 41 | } 42 | 43 | ``` 44 |   第一个首先是项目直接用到的依赖,也就是`dependencies`里的东西 45 | 分别有: 46 | ``` 47 | "dependencies": { 48 | "angular": "1.4.3", 49 | "angular-cache": "^4.5.0", 50 | "angular-cookies": "1.4.12", 51 | "angular-ui-router": "^0.3.2", 52 | "jquery": "^3.2.1" 53 | } 54 | ``` 55 |   我这里的`angular`和`angular-cookies`都用了具体的版本(就是版本号前面没有用符号`^`,直接写数字`1.4.3`),因为不合版本的这两个东西会跟`ionic`里的`angular-ui-router`发生冲突导致渲染失败。 56 |   而我这里也没有装`ionic`,是因为我直接引用的时候会报`can't resolve 'ionic'`的错误,我也不知道为什么,所以我是直接调用了`/app/assets/lib`里的`ionic.bundle.min.js`来引入的。请有找到原因的朋友麻烦告知一下我是为什么。 57 | 58 | 59 |   接下来是开发时用到的依赖: 60 | ``` 61 | "devDependencies": { 62 | "css-loader": "^0.26.4", 63 | "extract-text-webpack-plugin": "^3.0.1", 64 | "html-loader": "^0.4.4", 65 | "html-webpack-plugin": "^2.24.1", 66 | "style-loader": "^0.13.1", 67 | "url-loader": "^0.5.7", 68 | "webpack": "^3.1.0", 69 | "webpack-dev-server": "^2.9.2", 70 | "webpack-manifest-plugin": "^1.3.2", 71 | "webpack-merge": "^4.1.0" 72 | }, 73 | ``` 74 |   各种loader是必要的,因为webpack在打包的时候会把你项目里的非js文件转换出来然后打包在一起。 75 | 我们常用的loader有`css-loader`,`url-loader`,这两个分别是解析css和图片的。然后其他的loader我们要看项目需求来按需选取,比如我这里因为是angular 1.x的项目,里面还有挺多的html模板,所以我这里用到了`html-loader`来解析html。 76 |   其次是`webpack`——说句题外话,其实`webpack`一般都是用最新的,因为打包的环境跟所用的框架其实没有太多互相干扰的地方。我一开始想着用的1.x的`webpack`发现用起来不怎么方便,于是又改回最新的3.x。这也算是个人的一个小小心得吧。然后我们还用了`webpack-dev-server`。 77 | 这个是在我们开发的时候用的服务器,可以热替换更新代码。很方便,至于怎么用我后面会详细讲。然后就是`webpack-merge`这个东西是用来合并webpack配置,这个是在vue项目里看到,感觉还挺好用,就也模仿着用了。 78 | 最后就是各种插件,`extract-text-webpack-plugin`这个用来把css样式独立打包成一个css文件的插件,没有它的话,样式只会注入`index.html`做内联样式;`html-webpack-plugin`是用于把js注入到`index.html`里;`webpack-manifest-plugin`是用来生成网页的manifest文件的。 79 |   然后是写启动webpack的命令行,也就是上面的: 80 | ``` 81 | "scripts": { 82 | "build": "webpack --config ./build/webpack.prod.config.js", 83 | "dev": "set NODE_ENV=dev&& webpack-dev-server --config ./build/webpack.dev.config.js" 84 | }, 85 | ``` 86 | 这样写的意思是,当你输入`npm run ` + 你的命令名字就会让npm执行你对应命令的语句。 87 | 比如输入`npm run dev`,相当于你执行了上面那条`dev`对应的`set NODE_ENV=dev&& webpack-dev-server --config ./build/webpack.dev.config.js"`这条语句。 88 | 这里`dev`命令执行的是开发版本打包并生成开发的服务器;`build`命令执行的则是生产版本打包。 89 | 在打包开发版本的时候,用的是`webpack-dev-server`,我们让它按照`./build/webpack.dev.config.js`里的配置(下文会提到)来执行。 90 | 在打包生产环境,是直接运行`webpack`,让它按照`./build/webpack.prod.config.js`里的配置来执行。 91 | 92 |   关于这份`package.json`里其他的配置有问题的可以在issue里提哈~~ 93 | 94 | ### 然后来写webpack的配置文件 95 | #### 概述 96 |   安装了webpack,我们要配置好,让它按照我们的期望来工作。 97 | 一般我们都会用 `webpack.config.js`来命名webpack的配置文件,以免和其他配置文件搞混。 98 | 但是由于我们一般都会分开开发环境和生产环境,而对于这个两个环境打包我们要求会有点不一样。 99 | 开发环境我们希望它可以直接模仿生产环境放上服务器测试; 100 | 然后又想它可以一有改动就会自动打包更新显示在页面,不用我们手动刷新浏览器; 101 | 不希望它打包花的时间太长;如果出错会有相应的提示等等。 102 | 而生产环境我们想尽量压缩文件大小,生成manifest文件等等。 103 | 因此,我们就需要把开发打包和生产打包的配置分开来。这里我们就分开了 `webpack.dev.config.js`和`webpack.prod.config.js`两个文件。 104 | 但是还是有些配置是两个文件都会用到的,本着复用的精神,所以我们还有一个 `webapck.base.config.js`来记录公共的配置。 105 | 106 | #### webpack基本结构 107 |   webpack的配置主要分为几个部分: 108 | 1. webpack打包文件的入口(entry)。 109 | 2. webpack打包完文件后输出的出口(output)。 110 | 3. webpack在打包文件时的模块配置(module)。 111 | 4. webpack在打包文件时用到的插件(plugin)。 112 | 113 |   这四个是webpack配置的基本部分,写好了这四个基本可以打包成功了。 114 | 还有其他要用的配置后面会说到,其他配置没用到的可以看一下官方的文档[(3.x的官网)](http://webpack.github.io/docs/)/ [(个人觉得翻译的比较好的中文文档)](http://www.css88.com/doc/webpack/)。 115 | 接下来我们先来分析下开发环境和生成环境共用的部分配置。 116 | 首先入口文件一般都是一样的吧?然后打包时模块配置也是一样的,因为你打包时的文件都是一样的,所以设置也是一样的。 117 | 所以我们的 `webpack.base.config.js`是这样写的: 118 | ``` 119 | var path = require('path'); 120 | var root = path.resolve(__dirname, '../'); 121 | 122 | module.exports = { 123 | entry: { 124 | 'main': root + '\\app\\index.js', 125 | jquery:['jquery'], 126 | ionic:root+'\\app\\assets\\lib\\ionic\\release\\js\\ionic.bundle.min.js', 127 | datepicker:root+'\\app\\assets\\lib\\ionic-datepicker\\release\\ionic-datepicker.bundle.min.js', 128 | calendar_pk:root+'\\app\\assets\\lib\\calendar-pk\\release\\js\\calendar_pk.min.js' 129 | }, 130 | module: { 131 | loaders: [ 132 | { 133 | test: /\.(png|jpe?g|gif|woff|svg|eot|ttf)(\?.*)?$/, 134 | loader: 'url-loader', 135 | query: { 136 | limit: 10000, 137 | } 138 | }, 139 | { 140 | test: /\.html$/, 141 | loader: 'html-loader' 142 | } 143 | 144 | ] 145 | }, 146 | resolve: { 147 | extensions: ['.js', '.json'] 148 | } 149 | 150 | }; 151 | ``` 152 |   入口(entry)文件其实不一定是只有一个,我这里就有多个。只要路径分开写正确就可以了。 153 |   然后是模块(module)配置,webpack的思想是把工程所有的js都是模块,然后全部打包在一起。 154 | 所以遇到一些非js会有麻烦。但是他们早就预料这种情况,做出了一些系列的loader(加载器)来把一些非js文件做成webpack能打包的东西。 155 | 一般都会用到的是`css-loader`, `url-loader`。这两个分别用来解析项目里.css和图片字体之类的文件。 156 | 上面说过,由于项目中会有较多的.html文件要引用,所以我们还用了 `html-loader`。 157 | 我这里还有一个 `resolve`(解析)的配置,这个是用来js里引用文件的时候,不写后缀的话,webpack就会自动为其加上.js或.json的后缀,可以省一些写后缀的时间(✧◡✧)。 158 | 159 | #### 开发打包配置 160 |   我们的开发打包配置是这样的: 161 | ``` 162 | var baseconf = require('./webpack.base.config'); 163 | var merge = require('webpack-merge'); 164 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 165 | var webpack = require('webpack'); 166 | var server = require('./configDevServer'); 167 | var path = require('path'); 168 | var root = path.resolve(__dirname, '../'); 169 | 170 | var plugins = [ 171 | new webpack.DefinePlugin({ 172 | 'process.env': { 173 | NODE_ENV: JSON.stringify("development") 174 | } 175 | }), 176 | new webpack.optimize.UglifyJsPlugin({ 177 | compress: { 178 | warnings: false 179 | } 180 | }), 181 | new webpack.ProvidePlugin({ 182 | $: 'jquery', 183 | jQuery: 'jquery', 184 | 'window.jQuery': 'jquery', 185 | 'window.$': 'jquery' 186 | }), 187 | new webpack.optimize.CommonsChunkPlugin({ 188 | name: 'vendor', // 这公共代码的chunk名为'commons' 189 | filename: '[name].bundle.js', // 生成后的文件名,虽说用了[name],但实际上就是'commons.bundle.js'了 190 | minChunks: 3, // 设定要有4个chunk(即4个页面)加载的js模块才会被纳入公共代码。这数目自己考虑吧,我认为3-5比较合适。 191 | }), 192 | new HtmlWebpackPlugin({ 193 | filename: 'index.html', 194 | template: 'index.html', 195 | inject: true 196 | }), 197 | new webpack.HotModuleReplacementPlugin() 198 | ]; 199 | baseconf.module.loaders.push( 200 | { 201 | test: /\.css$/, 202 | loader: ['style-loader','css-loader'], 203 | } 204 | ); 205 | module.exports = merge(baseconf, { 206 | output: { 207 | path: root+"/dist", 208 | publicPath: "/", 209 | filename: "./js/[name].[chunkhash].js" 210 | }, 211 | devtool: 'cheap-module-eval-source-map', 212 | devServer: server, 213 | plugins: plugins, 214 | }); 215 | 216 | ``` 217 |   我们首先把基本的配置引进来。然后写插件(plugin),毕竟我们开发配置想实现的功能有部分需要插件来做。 218 | `webpack.DefinePlugin`是用来让webpack知道正在准备的是开发环境打包。某些框架会识别开发和生产环境,然后在我们开发的时候会给出相应的警告和提示,而在生产环境则会屏蔽这些内容。 219 | `webpack.ProvidePlugin`是当我们用到 `jQuery`之类的js库的时候,用到的相关符号都会自动进行引用,不会导致报错。 220 | `webpack.optimize.CommonsChunkPlugin`是用来提取我们代码里的公共用到的部分,避免代码重复打包,减少代码体积。 221 | `webpack.HotModuleReplacementPlugin`是用来启用我们的代码热替换功能,在我们改了代码之后开发服务器可以重新打包更新,浏览器自动刷新,把我们的改动显示在页面。 222 | `HtmlWebpackPlugin`是我们自己安装的插件,用来把生成的js自动插入到我们的html模板里面。 223 |   写完了插件之后,我们还要写输出(output)。这里指定下输出文件夹和输出的js名字即可。 224 | 然后是是开发工具(devtool)和开发服务器(dev-server),开发工具的意思是,webpack会根据打包的文件做出一个标识的map文件,如果代码出错的话,它会找出来,然后提示在什么地方。方便修改代码。 225 | 开发服务器是一个建立在本地的服务器,上面就是你的项目。搭配热替换功能,开发会很方便。这里顺带简单介绍下,开发服务器配置 `./build/configDevServer.js`: 226 | ``` 227 | const server={ 228 | contentBase:'/dist/', 229 | host: 'localhost',//服务主机 230 | port: 8089,//端口 231 | inline: true, // 可以监控js变化 232 | hot: true, // 热启动 233 | compress: true, 234 | watchContentBase: true, 235 | proxy: {//设置代理服务器,用于调试接口 236 | '/api':{ 237 | target:'http://www.baidu.com', 238 | pathRewrite:{"^/api": "/api"}//重写路径 239 | } 240 | } 241 | }; 242 | module.exports= server; 243 | ``` 244 |   可以看上面的备注来理解对应的配置项的意思。 245 |   上文我们装了个 `webpack-merge`这时就发挥作用了。正如它的名字一样,它会把两个webpack配置合并起来。然后输出。 246 | 这样我们的开发环境配置写好了 247 | 248 | #### 生产环境配置 249 |   同样,先上配置: 250 | ``` 251 | var baseconf = require('./webpack.base.config'); 252 | var path = require('path'); 253 | var root = path.resolve(__dirname, '../'); 254 | var merge = require('webpack-merge'); 255 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 256 | var webpack=require('webpack'); 257 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 258 | var ManifestPlugin = require('webpack-manifest-plugin'); 259 | var plugins = [ 260 | new webpack.DefinePlugin({ 261 | 'process.env': { 262 | NODE_ENV:JSON.stringify("development") 263 | } 264 | }), 265 | new webpack.optimize.UglifyJsPlugin({ 266 | compress: { 267 | warnings: false 268 | } 269 | }), 270 | new HtmlWebpackPlugin({ 271 | filename: 'index.html', 272 | template: 'index.html', 273 | inject: true 274 | }), 275 | new ExtractTextPlugin({ 276 | filename: './css/[name].css?[contenthash:8]', 277 | allChunks: true, 278 | }), 279 | new webpack.ProvidePlugin({ 280 | $: 'jquery', 281 | jQuery: 'jquery', 282 | 'window.jQuery': 'jquery', 283 | 'window.$': 'jquery', 284 | }), 285 | new webpack.optimize.CommonsChunkPlugin({ 286 | name: 'commons', // 这公共代码的chunk名为'commons' 287 | filename: './js/[name].bundle.js', // 生成后的文件名,虽说用了[name],但实际上就是'commons.bundle.js'了 288 | minChunks: 3, // 设定要有4个chunk(即4个页面)加载的js模块才会被纳入公共代码。这数目自己考虑吧,我认为3-5比较合适。 289 | }), 290 | new ManifestPlugin(path.join('dist', 'manifest.json')) 291 | ]; 292 | baseconf.module.rules.push( 293 | { test: /\.css$/, 294 | loader: ['style-loader','css-loader'] } 295 | ); 296 | module.exports=merge(baseconf,{ 297 | output: { 298 | path: root+"/dist", 299 | publicPath: "./", 300 | filename: "./js/[name].[chunkhash].js" 301 | }, 302 | devtool: false, 303 | plugins: plugins 304 | }); 305 | 306 | ``` 307 |   重复的插件我们就不说了,我们说说几个上面没有的插件。 `webpack.optimize.UglifyJsPlugin`是用来压缩混淆js代码的。 308 | `ExtractTextPlugin`是我们另外安装的,用来把打包的css独立出来成一个css文件。使用这个插件的时候,css的loader要相应做一下设置,所以可以看到 `css-loader`我没有放到公共配置,里面而是分开了。 309 | `ManifestPlugin`也是另外安装的,用来生成manifest缓存文件,使网站可以减少对静态资源的重复请求。 310 | 另外你可以发现这里devtool设成了false,没有设置devserver,因为不是生产所需要的,所以没有设置。 311 | 312 | #### 来跑一遍吧! 313 | 在你的入口的地方建立一个配置里的entry规定名字的js文件,就可以先跑一遍webpack。 314 | 如果webpack没有报错,就说明你的配置基本是对的。 315 | 316 | 接下来,我会就angular 1.x 用webpack打包打包遇到的坑来说一说,请看下一篇文章: 317 | 318 | [基于webpack构建的angular 1.x工程(angular篇)](./angular-m-part.md) 319 | 320 | 想看详细代码,可以访问我的项目地址 321 | [https://github.com/homerious/angular-ionic-webpack](https://github.com/homerious/angular-ionic-webpack) 322 | 323 | 有什么问题或者不对的地方欢迎指出,谢谢阅读! 324 | 325 | 本文原创,未经授权请勿转载。 -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-ionic-webpack", 3 | "version": "1.0.0", 4 | "description": "a project base on angular 1.x and webpack", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "webpack --config ./build/webpack.prod.config.js", 8 | "dev": "set NODE_ENV=dev&& webpack-dev-server --config ./build/webpack.dev.config.js" 9 | }, 10 | "devDependencies": { 11 | "css-loader": "^0.26.4", 12 | "extract-text-webpack-plugin": "^3.0.1", 13 | "html-loader": "^0.4.4", 14 | "html-webpack-plugin": "^2.24.1", 15 | "style-loader": "^0.13.1", 16 | "url-loader": "^0.5.7", 17 | "webpack": "^3.1.0", 18 | "webpack-dev-server": "^2.9.2", 19 | "webpack-manifest-plugin": "^1.3.2", 20 | "webpack-merge": "^4.1.0" 21 | }, 22 | "dependencies": { 23 | "angular": "1.4.3", 24 | "angular-cache": "^4.5.0", 25 | "angular-cookies": "1.4.12", 26 | "angular-ui-router": "^0.3.2", 27 | "jquery": "^3.2.1" 28 | }, 29 | "author": "homer", 30 | "license": "MIT" 31 | } 32 | --------------------------------------------------------------------------------