├── .babelrc
├── .gitignore
├── LICENSE
├── README.md
├── bower.json
├── dist
├── ionic-modal-select.js
├── ionic-modal-select.js.map
├── ionic-modal-select.min.js
└── ionic-modal-select.min.js.map
├── gulpfile.js
├── package.json
├── src
├── ionic-modal-select.js
├── main.js
├── modal-template-multiple.html
└── modal-template.html
├── tests
├── compile-directive.tests.js
└── my.conf.js
├── webpack.config.js
└── webpack.config.production.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015"]
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ModalSelectExample/*
2 | node_modules/*
3 | bower_components/*
4 |
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 INMAGIK srl
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ionic-modal-select
2 |
3 | Modal select for Ionic Framework based on [$ionicModal](http://ionicframework.com/docs/api/service/$ionicModal/)
4 |
5 | See all docs and examples on the [project site](http://inmagik.github.io/ionic-modal-select).
6 |
7 | We also have a simple [Codepen demo](http://codepen.io/bianchimro/pen/epYYQO?editors=101).
8 |
9 | 
10 |
11 | ## IMPORTANT NOTICE
12 |
13 | **In order to survive, this project needs**:
14 | * proper testing: see [issue #26](https://github.com/inmagik/ionic-modal-select/issues/26)
15 | * co-maintainers: see [issue #54](https://github.com/inmagik/ionic-modal-select/issues/54)
16 |
17 | Any help on this is greatly appreciated. Comment directly those issues or contact me directly at mauro.bianchi at inmagik.com if you are interested in helping with this.
18 |
19 | ## Features
20 |
21 | * supports long list of object via collection-repeat
22 | * optional search bar
23 | * supports unsetting the chosen value (optional)
24 | * customizable modal classes, modal header and footer classes
25 | * customizable buttons text
26 | * multiple selectable options (experimental)
27 |
28 | ## Usage
29 |
30 | Get the files from github or install from bower:
31 | ```
32 | bower install ionic-modal-select
33 | ```
34 |
35 |
36 | Include `ionic-modal-select.js` or its minified version in your index.html:
37 |
38 | ```html
39 |
40 |
41 |
42 | ```
43 |
44 |
45 | Add the module `ionic-modal-select` to your application dependencies:
46 |
47 | ```javascript
48 |
49 | angular.module('starter', ['ionic', 'ionic-modal-select'])
50 |
51 | ```
52 |
53 | And you're ready to go.
54 |
55 |
56 | ## Directives
57 |
58 | ### modal-select
59 |
60 | This directive will transform the element into a modal select: when clicking the element a select dialog will be open, with options presented in a clickable list. Once the user clicks on an option, it will be set on the bound model.
61 |
62 | For this to work the following conditions must apply:
63 |
64 | * The element you use this directive must be clickable.
65 | * The directive requires ngModel to be set on the element
66 | * The directive expects an inner element of class "option" to define the options template
67 |
68 | The final value bound to your model will be determined as follow:
69 |
70 | * if you set the attribute `option-getter` will be set as `getterFunction(selectedItem)`
71 | * if you set the attribute `option-property` will be set as `selectedItem[propertyName]`
72 | * otherwise it will be set as the full object
73 |
74 |
75 | In case of "multiple" selection mode, the user is allowed to select multiple options and
76 | the bound ng-model will be a list containing the selected options, with the same logic
77 | of getting the value.
78 |
79 |
80 | #### Options
81 |
82 | option|meaning|accepted values|default
83 | ---|---|---|---
84 | `options`|List of options to choose from|Array||
85 | `options-expression`|The expression indicating how to enumerate a the options collection, of the format `variable in expression` – where variable is the user defined loop variable and expression is a scope expression giving the collection to enumerate. For example: `album in artist.albums or album in artist.albums | orderBy:'name'`.|expression||
86 | `option-getter`|Optional method to get the value from the chosen item|function|not set|
87 | `option-property`|Optional property name to get as model value from the chosen item|string|not set|
88 | `multiple`|If set (to any value) enables "multiple" selection mode that allows the user to select more than one option. For each option, a checkbox will be rendered. *This feature is still experimental*. |string|not set|
89 | `modal-class`|The class for the modal (set on ``|string|''
90 | `selected-class`|The class applied to the currently selected option (if any) in the modal list|string|'option-selected'
91 | `on-select`|Callback triggered on object select. Takes two arguments, `newValue` and `oldValue` with obvious meaning.|function call with arguments `newValue` and `oldValue`|not set
92 | `on-reset`|Callback triggered when value is resetted using the relevant ui interface. Takes no arguments.|function call|not set
93 | `on-close`|Callback triggered when modal is closed (in any way, uses 'modal.hidden' ionic event). Takes no arguments.|function call|not set
94 | `modal-title`|The title shown on the modal header|string|'Select an option'
95 | `header-footer-class`|The class for header and footer of the modal|string|'bar-stable'
96 | `cancel-button`|Text of the button for closing the modal without changing the value|string|'Cancel'
97 | `reset-button`|Text of the button for unsetting value in the modal dialog|string|'Reset'
98 | `ok-button`|Text of the button to accept the multiple selection. *Appears only when multiple true*. |string|'Cancel'
99 | `hide-reset`|Hides the button for unsetting value in the modal dialog|string. Set to 'true' for hiding the button|false
100 | `use-collection-repeat`|Forces use of collection-repeat or ng-repeat for rendering options in the modal.| string "true", "false" | not set (automatically set according to number of options and `short-list-break` attribute)
101 | `short-list-break`|The maximum number of item in list to be rendered with `ng-repeat`.(if `use-collection-repeat` is not set) If the list has a number of options greater than this attribute it will be rendered with ionic `collection-repeat` directive instead. (see also `load-list-message` option)|integer|10
102 | `load-list-message`|Message to be shown when loading a long list of options in the modal|string|'Loading'
103 | `has-search`|Whether to show a search bar to filter options.|set to "true" for showing the search bar|undefined
104 | `search-placeholder`|String placeholder in search bar.|string|'Search'
105 | `sub-header-class`|Class to be applied to the subheader containing the search bar (makes sense only if `has-search="true`) |string|'bar-stable'
106 | `cancel-search-button`|Text for the button for clearing search text (makes sense only if `has-search="true`) |string|'Clear'
107 | `clear-search-on-select`|Tells the directive to not clear the search bar content after user selection. Set to `false` to prevent clearing the search text.|boolean|true
108 | `search-properties`|Array of properties for the search. For example: In your controller `$scope.searchProperties = ['property1', 'property2'];` and in template attributes `search-properties="searchProperties"`|Array
109 |
110 |
111 | ### Passing in options
112 |
113 | The `modal-select` directive must be provided with a set of options to choose from
114 |
115 | This can be done in two ways:
116 |
117 | * via the `options` attribute, that accepts an array of values or objects. The directive will watch for changes in this array and modify its options accordingly.
118 | * via the `options-expression` attribute, that accepts an expression similar to what you would use with ionic `collection-repeat` directive, of the format `variable in expression` – where variable is the user defined loop variable and expression is a scope expression giving the collection to enumerate. For example: `album in artist.albums or album in artist.albums | orderBy:'name'`. This allows you to apply ordering or filtering without acting on the original array.
119 |
120 |
121 | ### Options templates
122 |
123 | This directive expects to find a single inner element of class "option" that is used to define the template of the options that can be selected. Options will be rendered as items into a list in the modal (The content of each option, rendered with your template, is wrapped in an element of class 'item item-text wrap' and the original ".option" element is removed).
124 |
125 | For example:
126 | ```html
127 |
133 | ```
134 |
135 | Will be rendered in the modal as :
136 |
137 | ```html
138 |
139 | {{option}}
140 |
141 | ```
142 |
143 | ## Multiple selection mode
144 | From version 1.3.1, setting `multiple` attribute to any value other than "undefined" will enable the multiple selection on the widget. In this case, the user is allowed to select more than one option and options will be rendered with checkboxes in the selection modal. *This feature is still experimental*.
145 |
146 |
147 | ## Search bar
148 | From version 1.1.0 you can include a search bar into the modal for filtering options by simply adding the attribute `has-search="true"` to your `modal-select` element.
149 |
150 | Filtering is implemented with the angular `filter` filter, which searches recursively in all properties of the objects passed in as options. This means that you cannot search on "computed properties" right now. For example if you are using a custom setter you will be only able to search the original properties of the options.
151 |
152 |
153 | ### Examples
154 | #### Simplest one.
155 | This example shows a modal for choosing a number between 1 and 5.
156 |
157 | In your controller:
158 |
159 | ```js
160 | $scope.selectables = [1,2,3,4,5];
161 | ```
162 | In your template:
163 |
164 | ```html
165 |
171 | ```
172 |
173 | #### Including a search bar
174 | To include a search bar in the previous example, just add `has-search="true"`:
175 |
176 | ```html
177 |
183 | ```
184 |
185 |
186 | #### Objects as options
187 | In the following example we use some objects as options.
188 |
189 |
190 | In your controller:
191 |
192 | ```js
193 | $scope.selectables = [
194 | { name: "Mauro", role : "navigator"},
195 | { name: "Silvia", role : "chef"},
196 | { name: "Merlino", role : "canaglia"}
197 | ];
198 | ```
199 |
200 | We'll explore different possibilities we have with this options.
201 |
202 | ##### 1. Setting the full object
203 |
204 | If we do not set `option-getter` or `option-property` attributes, the model is assigned to the full option object when an option is selected.
205 |
206 | ```html
207 |
213 | ```
214 |
215 |
216 |
217 |
218 | ##### 2. Setting a property
219 | If `option-property` attribute is set to a string, the bound model assigned that property of the option object when an option is selected. For example if we set `option-getter="name"`, we get back the 'name' property of our options.
220 |
221 | ```html
222 |
228 | ```
229 |
230 | ##### 3. Custom setter
231 | If a function call is passed via `option-getter` attribute, the bound model assignment is done by calling this function with the selected option as the only argument (named 'option'). For example if we do this in our controller:
232 |
233 | ```javascript
234 | $scope.getOption = function(option){
235 | return option.name + ":" + option.role;
236 | };
237 | ```
238 |
239 | ```html
240 |
246 | ```
247 |
248 | ##### 4. Specify the properties for search
249 | Specify in the array the properties' name for search `$scope.search_properties = ['propertie_1', 'propertie_2', '...'];`:
250 | ```javascript
251 | $scope.search_properties = ['name'];
252 | ```
253 | ```html
254 |
260 | ```
261 |
262 | We get back the phrase "Mauro:navigator", "Silvia:chef" or "Merlino:canaglia" if we click the previous defined options.
263 |
264 |
265 | ##### More examples [on the project site](http://inmagik.github.io/ionic-modal-select).
266 |
267 |
268 | ## Maintenance and support
269 | This project is maintained by [INMAGIK](https://www.inmagik.com).
270 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ionic-modal-select",
3 | "version": "1.3.1-alpha.0",
4 | "homepage": "https://github.com/inmagik/ionic-modal-select",
5 | "authors": [
6 | "Mauro Bianchi "
7 | ],
8 | "description": "Modal select for ionic framework",
9 | "main": [
10 | "dist/ionic-modal-select.js"
11 | ],
12 | "keywords": [
13 | "ionic",
14 | "modal",
15 | "select"
16 | ],
17 | "license": "MIT",
18 | "ignore": [
19 | "**/.*",
20 | "node_modules",
21 | "bower_components",
22 | "test",
23 | "tests"
24 | ],
25 | "devDependencies": {
26 | "ionic": "~1.1.1",
27 | "angular-mocks": "1.4.3"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/dist/ionic-modal-select.js:
--------------------------------------------------------------------------------
1 | /******/ (function(modules) { // webpackBootstrap
2 | /******/ // The module cache
3 | /******/ var installedModules = {};
4 | /******/
5 | /******/ // The require function
6 | /******/ function __webpack_require__(moduleId) {
7 | /******/
8 | /******/ // Check if module is in cache
9 | /******/ if(installedModules[moduleId])
10 | /******/ return installedModules[moduleId].exports;
11 | /******/
12 | /******/ // Create a new module (and put it into the cache)
13 | /******/ var module = installedModules[moduleId] = {
14 | /******/ exports: {},
15 | /******/ id: moduleId,
16 | /******/ loaded: false
17 | /******/ };
18 | /******/
19 | /******/ // Execute the module function
20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
21 | /******/
22 | /******/ // Flag the module as loaded
23 | /******/ module.loaded = true;
24 | /******/
25 | /******/ // Return the exports of the module
26 | /******/ return module.exports;
27 | /******/ }
28 | /******/
29 | /******/
30 | /******/ // expose the modules object (__webpack_modules__)
31 | /******/ __webpack_require__.m = modules;
32 | /******/
33 | /******/ // expose the module cache
34 | /******/ __webpack_require__.c = installedModules;
35 | /******/
36 | /******/ // __webpack_public_path__
37 | /******/ __webpack_require__.p = "";
38 | /******/
39 | /******/ // Load entry module and return exports
40 | /******/ return __webpack_require__(0);
41 | /******/ })
42 | /************************************************************************/
43 | /******/ ([
44 | /* 0 */
45 | /***/ function(module, exports, __webpack_require__) {
46 |
47 | "use strict";
48 |
49 | __webpack_require__(1);
50 |
51 | /***/ },
52 | /* 1 */
53 | /***/ function(module, exports, __webpack_require__) {
54 |
55 | 'use strict';
56 |
57 | compile.$inject = ["$compile"];
58 | modalSelect.$inject = ["$ionicModal", "$timeout", "$filter", "$parse", "$templateCache"];
59 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; };
60 |
61 | /*!
62 | * Copyright 2015 Inmagik SRL.
63 | * http://www.inmagik.com/
64 | *
65 | * ionic-modal-select, v1.3.2
66 | * Modal select directive for Ionic framework.
67 | *
68 | * By @bianchimro
69 | *
70 | * Licensed under the MIT license. Please see LICENSE for more information.
71 | *
72 | */
73 |
74 | angular.module('ionic-modal-select', []).directive('compile', compile).directive('modalSelect', modalSelect);
75 |
76 | function compile($compile) {
77 | return function (scope, iElement, iAttrs) {
78 | var x = scope.$watch(function (scope) {
79 | // watch the 'compile' expression for changes
80 | return scope.$eval(iAttrs.compile);
81 | }, function (value) {
82 | // when the 'compile' expression changes
83 | // assign it into the current DOM
84 | iElement.html(value);
85 |
86 | // compile the new DOM and link it to the current
87 | // scope.
88 | // NOTE: we only compile .childNodes so that
89 | // we don't get into infinite loop compiling ourselves
90 | $compile(iElement.contents())(scope);
91 |
92 | //deactivate watch if "compile-once" is set to "true"
93 | if (iAttrs.compileOnce === 'true') {
94 | x();
95 | }
96 | });
97 | };
98 | }
99 |
100 | function modalSelect($ionicModal, $timeout, $filter, $parse, $templateCache) {
101 |
102 | var modalTemplateMultiple = __webpack_require__(2);
103 | var modalTemplate = __webpack_require__(3);
104 |
105 | return {
106 | restrict: 'A',
107 | require: 'ngModel',
108 | scope: {
109 | initialOptions: "=options",
110 | optionGetter: "&",
111 | searchFilters: "=searchFilters",
112 | searchProperties: '=',
113 | onSelect: "&",
114 | onSearch: "&",
115 | onReset: "&",
116 | onClose: "&"
117 | },
118 | link: function link(scope, iElement, iAttrs, ngModelController, transclude) {
119 |
120 | var shortList = true;
121 | var shortListBreak = iAttrs.shortListBreak ? parseInt(iAttrs.shortListBreak) : 10;
122 | var setFromProperty = iAttrs.optionProperty;
123 | var onOptionSelect = iAttrs.optionGetter;
124 | var clearSearchOnSelect = iAttrs.clearSearchOnSelect !== "false" ? true : false;
125 | var searchProperties = scope.searchProperties ? scope.searchProperties : false;
126 |
127 | //multiple values settings.
128 | var multiple = iAttrs.multiple ? true : false;
129 | if (multiple) {
130 | scope.isChecked = {};
131 | }
132 | var multipleNullValue = iAttrs.multipleNullValue ? scope.$eval(iAttrs.multipleNullValue) : [];
133 |
134 | scope.ui = {
135 | modalTitle: iAttrs.modalTitle || 'Select an option',
136 | okButton: iAttrs.okButton || 'OK',
137 | hideReset: iAttrs.hideReset !== "true" ? false : true,
138 | resetButton: iAttrs.resetButton || 'Reset',
139 | cancelButton: iAttrs.cancelButton || 'Cancel',
140 | loadListMessage: iAttrs.loadListMessage || 'Loading',
141 | modalClass: iAttrs.modalClass || '',
142 | headerFooterClass: iAttrs.headerFooterClass || 'bar-stable',
143 | value: null,
144 | selectedClass: iAttrs.selectedClass || 'option-selected',
145 | itemClass: iAttrs.itemClass || 'item item-text-wrap',
146 | searchTemplate: iAttrs.searchTemplate || (multiple ? modalTemplateMultiple : modalTemplate),
147 |
148 | //search stuff
149 | hasSearch: iAttrs.hasSearch !== "true" ? false : true,
150 | searchValue: '',
151 | searchPlaceholder: iAttrs.searchPlaceholder || 'Search',
152 | subHeaderClass: iAttrs.subHeaderClass || 'bar-stable',
153 | cancelSearchButton: iAttrs.cancelSearchButton || 'Clear'
154 |
155 | };
156 |
157 | var allOptions = [];
158 | scope.options = [];
159 |
160 | if (iAttrs.optionsExpression) {
161 | var optionsExpression = iAttrs.optionsExpression;
162 | var match = optionsExpression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);
163 | if (!match) {
164 | throw new Error("collection-repeat expected expression in form of '_item_ in " + "_collection_[ track by _id_]' but got '" + iAttrs.optionsExpression + "'.");
165 | }
166 | //var keyExpr = match[1];
167 | var listExpr = match[2];
168 | var listGetter = $parse(listExpr);
169 | var s = iElement.scope();
170 |
171 | scope.$watch(function () {
172 | return listGetter(s);
173 | }, function (nv, ov) {
174 | initialOptionsSetup(nv);
175 | updateListMode();
176 | }, true);
177 | } else {
178 | scope.$watchCollection('initialOptions', function (nv) {
179 | initialOptionsSetup(nv);
180 | updateListMode();
181 | });
182 | }
183 |
184 | //#TODO: this is due to different single vs multiple template
185 | //but adds lots of complexity here and in search
186 | function initialOptionsSetup(nv) {
187 | nv = nv || [];
188 | if (!multiple) {
189 | allOptions = angular.copy(nv);
190 | scope.options = angular.copy(nv);
191 | } else {
192 | allOptions = nv.map(function (item, idx) {
193 | return [idx, angular.copy(item)];
194 | });
195 | scope.options = angular.copy(allOptions);
196 | }
197 | }
198 |
199 | // getting options template
200 | var opt = iElement[0].querySelector('.option');
201 | if (!opt) {
202 | throw new Error({
203 | name: 'modalSelectError:noOptionTemplate',
204 | message: 'When using modalSelect directive you must include an element with class "option"\n\t\t\t\t\t\t to provide a template for your select options.',
205 | toString: function toString() {
206 | return this.name + " " + this.message;
207 | }
208 | });
209 | }
210 | scope.inner = angular.element(opt).html();
211 |
212 | //add support for .remove for older devices
213 | if (!('remove' in Element.prototype)) {
214 | Element.prototype.remove = function () {
215 | this.parentNode.removeChild(this);
216 | };
217 | }
218 |
219 | angular.element(opt).remove();
220 |
221 | var notFound = iElement[0].querySelector('.not-found');
222 | if (notFound) {
223 | scope.notFound = angular.element(notFound).html();
224 | angular.element(notFound).remove();
225 | }
226 |
227 | function updateListMode() {
228 | //shortList controls wether using ng-repeat instead of collection-repeat
229 | if (iAttrs.useCollectionRepeat === "true") {
230 | shortList = false;
231 | } else if (iAttrs.useCollectionRepeat === "false") {
232 | shortList = true;
233 | } else {
234 | if (typeof scope.options !== "undefined") {
235 | shortList = !!(scope.options.length < shortListBreak);
236 | }
237 | }
238 |
239 | scope.ui.shortList = shortList;
240 | }
241 |
242 | ngModelController.$render = function () {
243 | scope.ui.value = ngModelController.$viewValue;
244 | };
245 |
246 | var getSelectedValue = scope.getSelectedValue = function (option) {
247 | var val = null;
248 | if (option === null || option === undefined) {
249 | return option;
250 | }
251 | if (onOptionSelect) {
252 | return scope.optionGetter({ option: option });
253 | }
254 | if (setFromProperty) {
255 | val = option[setFromProperty];
256 | } else {
257 | val = option;
258 | }
259 | return val;
260 | };
261 |
262 | scope.setOption = function (option) {
263 | var oldValue = ngModelController.$viewValue;
264 | var val = getSelectedValue(option);
265 | ngModelController.$setViewValue(val);
266 | ngModelController.$render();
267 |
268 | if (scope.onSelect) {
269 | scope.onSelect({ newValue: val, oldValue: oldValue });
270 | }
271 | scope.modal.hide().then(function () {
272 | scope.showList = false;
273 | if (scope.ui.hasSearch) {
274 | if (clearSearchOnSelect) {
275 | scope.ui.searchValue = '';
276 | }
277 | }
278 | });
279 | };
280 |
281 | // Filter object {id: , active: }
282 | // Used as auxiliary query params when querying server for search results
283 | scope.setFilter = function (filterId) {
284 | angular.forEach(scope.searchFilters, function (filter) {
285 | if (filter.id == filterId) {
286 | filter.active = !filter.active;
287 | } else {
288 | filter.active = false;
289 | }
290 | });
291 |
292 | // Trigger another search when the search filters change
293 | if (scope.onSearch) {
294 | scope.onSearch({ query: scope.ui.searchValue });
295 | }
296 | };
297 |
298 | scope.unsetValue = function () {
299 | $timeout(function () {
300 | ngModelController.$setViewValue("");
301 | ngModelController.$render();
302 | scope.modal.hide();
303 | scope.showList = false;
304 | if (scope.onReset && angular.isFunction(scope.onReset)) {
305 | scope.onReset();
306 | }
307 | });
308 | };
309 |
310 | scope.setValues = function () {
311 | var checkedItems = [];
312 | angular.forEach(scope.isChecked, function (v, k) {
313 | if (v) {
314 | checkedItems.push(allOptions[k][1]);
315 | }
316 | });
317 | var oldValues = ngModelController.$viewValue;
318 | var vals = checkedItems.map(function (item) {
319 | return getSelectedValue(item);
320 | });
321 | ngModelController.$setViewValue(vals);
322 | ngModelController.$render();
323 |
324 | if (scope.onSelect) {
325 | scope.onSelect({ newValue: vals, oldValue: oldValues });
326 | }
327 | scope.modal.hide().then(function () {
328 | scope.showList = false;
329 | if (scope.ui.hasSearch) {
330 | if (clearSearchOnSelect) {
331 | scope.ui.searchValue = '';
332 | }
333 | }
334 | });
335 | };
336 |
337 | scope.unsetValues = function () {
338 | $timeout(function () {
339 | ngModelController.$setViewValue(multipleNullValue);
340 | ngModelController.$render();
341 | scope.isChecked = {};
342 | scope.modal.hide();
343 | scope.showList = false;
344 | if (scope.onReset && angular.isFunction(scope.onReset)) {
345 | scope.onReset();
346 | }
347 | });
348 | };
349 |
350 | scope.closeModal = function () {
351 | scope.modal.hide().then(function () {
352 | scope.showList = false;
353 | });
354 | };
355 |
356 | scope.compareValues = function (a, b) {
357 | return angular.equals(a, b);
358 | };
359 |
360 | //loading the modal
361 | var modalTpl = null;
362 | if (iAttrs.searchTemplate) {
363 | scope.modal = $ionicModal.fromTemplate($templateCache.get(iAttrs.searchTemplate), { scope: scope });
364 | } else {
365 | modalTpl = multiple ? modalTemplateMultiple : modalTemplate;
366 | scope.modal = $ionicModal.fromTemplate(modalTpl, { scope: scope });
367 | }
368 |
369 | var hiddenCb = null;
370 | scope.$on('$destroy', function () {
371 | if (hiddenCb) {
372 | hiddenCb();
373 | hiddenCb = null;
374 | }
375 | scope.modal.remove();
376 | });
377 |
378 | if (scope.onClose && angular.isFunction(scope.onClose)) {
379 | hiddenCb = scope.$on('modal.hidden', function () {
380 | scope.onClose();
381 | });
382 | }
383 |
384 | iElement.on('click', function () {
385 | if (shortList) {
386 | scope.showList = true;
387 | scope.modal.show();
388 | } else {
389 | scope.modal.show().then(function () {
390 | scope.showList = true;
391 | scope.ui.shortList = shortList;
392 | });
393 | }
394 | });
395 |
396 | //filter function
397 | if (scope.ui.hasSearch) {
398 | scope.$watch('ui.searchValue', function (nv) {
399 | var whatToSearch;
400 | if (!multiple) {
401 | whatToSearch = allOptions;
402 | } else {
403 | whatToSearch = allOptions.map(function (item) {
404 | return item[1];
405 | });
406 | }
407 |
408 | if (iAttrs.onSearch) {
409 | scope.onSearch({ query: nv });
410 | } else {
411 | var filteredOpts = $filter('filter')(whatToSearch, nv, function (actual, expected) {
412 | if (!actual) {
413 | // if actual is an empty string, empty object, null, or undefined
414 | return false;
415 | }
416 | if (searchProperties) {
417 | if ((typeof actual === 'undefined' ? 'undefined' : _typeof(actual)) == 'object') {
418 | for (var i = 0; i < searchProperties.length; i++) {
419 | if (actual[searchProperties[i]] && actual[searchProperties[i]].toLowerCase().indexOf(expected.toLowerCase()) >= 0) {
420 | return true;
421 | }
422 | }
423 | }
424 | return false;
425 | } else {
426 | if (actual.toString().toLowerCase().indexOf(expected.toLowerCase()) >= 0) {
427 | return true;
428 | }
429 | }
430 | return false;
431 | });
432 |
433 | var oldLen = scope.options.length;
434 | if (!multiple) {
435 | scope.options = filteredOpts;
436 | } else {
437 | //#TODO: lots of loops here!
438 | var newOpts = [];
439 | angular.forEach(filteredOpts, function (item) {
440 | var originalItem = allOptions.find(function (it) {
441 | return it[1] == item;
442 | });
443 | if (originalItem) {
444 | newOpts.push(originalItem);
445 | }
446 | });
447 | scope.options = newOpts;
448 | }
449 | if (oldLen != scope.options.length) {
450 | //#todo: should resize scroll or scroll up here
451 | }
452 | }
453 | });
454 | scope.clearSearch = function () {
455 | scope.ui.searchValue = '';
456 | };
457 | }
458 |
459 | scope.copyOpt = function (option) {
460 | return angular.copy(option);
461 | };
462 |
463 | //#TODO ?: WRAP INTO $timeout?
464 | ngModelController.$render();
465 | }
466 | };
467 | }
468 |
469 | /***/ },
470 | /* 2 */
471 | /***/ function(module, exports) {
472 |
473 | module.exports = " \n\n \n