├── .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 |
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" +
453 | " {{::day.date | date: cc.formatDayHeader | uppercase}}\n" +
454 | " | \n" +
455 | "
\n" +
456 | " \n" +
457 | " \n" +
458 | " \n" +
459 | " 第 {{view.dates[7*i].date | date : 'w'}}周 | \n" +
460 | " {{date.date | date : cc.formatDay}} | \n" +
464 | "
\n" +
465 | " \n" +
466 | "
\n" +
467 | " \n" +
468 | " \n" +
469 | " \n" +
470 | " | \n" +
471 | " \n" +
472 | " {{::day.date | date: cc.formatDayHeader | uppercase}}\n" +
473 | " | \n" +
474 | "
\n" +
475 | " \n" +
476 | " \n" +
477 | " \n" +
478 | " 第 {{view.dates[7*i].date | date : 'w'}}周 | \n" +
479 | " {{view.dates[7*i+j].date | date : cc.formatDay}} | \n" +
480 | "
\n" +
481 | " \n" +
482 | "
\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 {{::day.date | date: cc.formatDayHeader | uppercase}}\n | \n
\n \n \n \n SEM {{view.dates[7*i].date | date : \'w\'}} | \n {{date.date | date : cc.formatDay}} | \n
\n \n
\n \n \n \n | \n \n {{::day.date | date: cc.formatDayHeader | uppercase}}\n | \n
\n \n \n \n SEM {{view.dates[7*i].date | date : \'w\'}} | \n {{view.dates[7*i+j].date | date : cc.formatDay}} | \n
\n \n
\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",'{{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 | '' +
24 | '
';
25 | } else {
26 | filterBarTemplate =
27 | '' +
28 | '' +
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 |
76 |
77 |
78 |
79 |
80 |
81 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
151 |
152 |
241 |
242 |
252 |
253 |
256 |
257 |
258 |
259 |
260 |
263 |
266 |
267 |
268 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
Enter the password to open this PDF file:
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
298 |
301 |
302 |
305 |
308 |
311 |
314 |
315 |
Creation Date: -
316 |
317 |
318 |
Modification Date: -
319 |
320 |
323 |
324 |
325 |
PDF Producer: -
326 |
327 |
328 |
PDF Version: -
329 |
330 |
331 |
Page Count: -
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
351 |
409 |
410 |
411 | Preparing document for printing...
412 |
416 |
417 |
418 |
419 |
420 |
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 |
--------------------------------------------------------------------------------