lanes 0.1.9.5 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (304) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -0
  3. data/Rakefile +1 -1
  4. data/client/lanes/Boot.cjsx +3 -3
  5. data/client/lanes/Config.coffee +38 -3
  6. data/client/lanes/access/Extension.coffee +1 -1
  7. data/client/lanes/access/LoginDialog.cjsx +57 -47
  8. data/client/lanes/access/Roles.coffee +2 -2
  9. data/client/lanes/access/User.coffee +4 -3
  10. data/client/lanes/access/screens/user-management/UserManagement.cjsx +12 -11
  11. data/client/lanes/components/calendar/Calendar.cjsx +16 -0
  12. data/client/lanes/components/calendar/index.js +3 -0
  13. data/client/lanes/components/calendar/styles.scss +3 -0
  14. data/client/lanes/components/grid/Body.cjsx +86 -0
  15. data/client/lanes/components/grid/CellStyles.coffee +20 -0
  16. data/client/lanes/components/grid/EditingMixin.cjsx +84 -24
  17. data/client/lanes/components/grid/Editor.cjsx +45 -0
  18. data/client/lanes/components/grid/Grid.cjsx +62 -104
  19. data/client/lanes/components/grid/Header.cjsx +35 -0
  20. data/client/lanes/components/grid/PopOverMixin.cjsx +19 -9
  21. data/client/lanes/components/grid/PopoverEditor.cjsx +7 -1
  22. data/client/lanes/components/grid/RowEditor.cjsx +1 -1
  23. data/client/lanes/components/grid/Selections.cjsx +39 -0
  24. data/client/lanes/components/grid/Toolbar.cjsx +24 -5
  25. data/client/lanes/components/grid/editors.scss +22 -50
  26. data/client/lanes/components/grid/index.js +0 -1
  27. data/client/lanes/components/grid/row-editor.scss +68 -0
  28. data/client/lanes/components/grid/styles.scss +79 -3
  29. data/client/lanes/components/modal/Modal.cjsx +64 -24
  30. data/client/lanes/components/modal/styles.scss +12 -0
  31. data/client/lanes/components/record-finder/Clause.cjsx +11 -4
  32. data/client/lanes/components/record-finder/Dialog.cjsx +23 -24
  33. data/client/lanes/components/record-finder/RecordFinder.cjsx +45 -14
  34. data/client/lanes/components/record-finder/styles.scss +9 -6
  35. data/client/lanes/components/select-field/SelectField.cjsx +108 -53
  36. data/client/lanes/components/select-field/styles.scss +19 -0
  37. data/client/lanes/components/shared/ControlLabel.cjsx +45 -0
  38. data/client/lanes/components/shared/DateTime.cjsx +48 -0
  39. data/client/lanes/components/shared/DisplayValue.cjsx +16 -0
  40. data/client/lanes/components/shared/FieldMixin.cjsx +54 -23
  41. data/client/lanes/components/shared/FieldSet.cjsx +12 -35
  42. data/client/lanes/components/shared/FieldWrapper.cjsx +13 -0
  43. data/client/lanes/components/shared/FormGroup.cjsx +37 -0
  44. data/client/lanes/components/shared/Icon.cjsx +20 -0
  45. data/client/lanes/components/shared/ImageSaver.cjsx +33 -0
  46. data/client/lanes/components/shared/Input.cjsx +19 -0
  47. data/client/lanes/components/shared/InputFieldMixin.cjsx +48 -0
  48. data/client/lanes/components/shared/JobProgress.cjsx +27 -0
  49. data/client/lanes/components/shared/NetworkActivityOverlay.cjsx +58 -0
  50. data/client/lanes/components/shared/NumberInput.cjsx +29 -0
  51. data/client/lanes/components/shared/ResizeSensor.cjsx +11 -0
  52. data/client/lanes/components/shared/ScreenWrapper.cjsx +13 -0
  53. data/client/lanes/components/shared/Throbber.cjsx +3 -0
  54. data/client/lanes/components/shared/ToggleField.cjsx +33 -0
  55. data/client/lanes/components/shared/Tooltip.cjsx +2 -2
  56. data/client/lanes/components/shared/fields.scss +75 -13
  57. data/client/lanes/components/shared/fieldset.scss +3 -5
  58. data/client/lanes/components/shared/image-saver.scss +38 -0
  59. data/client/lanes/components/shared/index.js +2 -0
  60. data/client/lanes/{styles/plugins → components/shared}/overlay.scss +17 -4
  61. data/client/lanes/components/shared/resize-sensor.scss +30 -0
  62. data/client/lanes/components/shared/styles.scss +13 -0
  63. data/client/lanes/components/shared/throbber.scss +53 -0
  64. data/client/lanes/components/toolbar/RemoteChangeSets.cjsx +21 -48
  65. data/client/lanes/components/toolbar/SaveButton.cjsx +24 -0
  66. data/client/lanes/components/toolbar/Toolbar.cjsx +24 -37
  67. data/client/lanes/components/toolbar/changes-notification.scss +10 -6
  68. data/client/lanes/components/toolbar/styles.scss +29 -9
  69. data/client/lanes/extension/Base.coffee +4 -5
  70. data/client/lanes/index.js +0 -1
  71. data/client/lanes/index.scss.erb +10 -1
  72. data/client/lanes/lib/HotReload.coffee +13 -15
  73. data/client/lanes/lib/MakeBaseClass.coffee +6 -1
  74. data/client/lanes/lib/development.coffee +2 -0
  75. data/client/lanes/lib/dom-polyfills.coffee +5 -0
  76. data/client/lanes/lib/dom.coffee +38 -9
  77. data/client/lanes/lib/format.coffee +11 -0
  78. data/client/lanes/lib/index.js.erb +2 -0
  79. data/client/lanes/lib/production.coffee +6 -0
  80. data/client/lanes/lib/utilFunctions.coffee +50 -15
  81. data/client/lanes/models/AssociationMap.coffee +122 -46
  82. data/client/lanes/models/AssociationProxy.coffee +147 -0
  83. data/client/lanes/models/Base.coffee +97 -85
  84. data/client/lanes/models/ChangeMonitor.coffee +7 -3
  85. data/client/lanes/models/ChangeSet.coffee +2 -2
  86. data/client/lanes/models/Collection.coffee +49 -6
  87. data/client/lanes/models/JobStatus.coffee +32 -0
  88. data/client/lanes/models/PubSub.coffee +7 -5
  89. data/client/lanes/models/Query.coffee +115 -34
  90. data/client/lanes/models/ServerCache.coffee +67 -52
  91. data/client/lanes/models/State.coffee +97 -0
  92. data/client/lanes/models/Sync.coffee +18 -13
  93. data/client/lanes/models/SystemSettings.coffee +0 -0
  94. data/client/lanes/models/index.js +1 -0
  95. data/client/lanes/models/mixins/FileSupport.coffee +60 -0
  96. data/client/lanes/models/mixins/HasCodeField.coffee +13 -6
  97. data/client/lanes/models/query/ArrayResult.coffee +188 -0
  98. data/client/lanes/models/query/CollectionResult.coffee +71 -0
  99. data/client/lanes/models/query/Result.coffee +9 -0
  100. data/client/lanes/react/Component.coffee +7 -3
  101. data/client/lanes/react/PubSub.coffee +7 -7
  102. data/client/lanes/react/Root.cjsx +1 -4
  103. data/client/lanes/react/Screen.coffee +1 -0
  104. data/client/lanes/react/TypeValidators.coffee +3 -3
  105. data/client/lanes/react/Viewport.coffee +41 -7
  106. data/client/lanes/react/index.js +0 -1
  107. data/client/lanes/react/mixins/Access.coffee +4 -1
  108. data/client/lanes/react/mixins/Data.coffee +40 -25
  109. data/client/lanes/react/mixins/FieldErrors.coffee +27 -0
  110. data/client/lanes/react/mixins/RelayEditingState.coffee +4 -0
  111. data/client/lanes/react/mixins/Screen.coffee +14 -0
  112. data/client/lanes/react/mixins/Viewport.coffee +9 -3
  113. data/client/lanes/screens/ChangeListener.coffee +3 -3
  114. data/client/lanes/screens/Commands.coffee +14 -7
  115. data/client/lanes/screens/CommonComponents.cjsx +20 -0
  116. data/client/lanes/screens/Definitions.coffee +64 -20
  117. data/client/lanes/screens/SystemSettings.cjsx +56 -0
  118. data/client/lanes/screens/UserPreferences.cjsx +38 -0
  119. data/client/lanes/screens/index.js +3 -0
  120. data/client/lanes/screens/styles.scss +2 -1
  121. data/client/lanes/styles/fonts.scss +1 -0
  122. data/client/lanes/styles/global.scss +2 -1
  123. data/client/lanes/styles/global/flexbox.scss +16 -0
  124. data/client/lanes/styles/global/styles.scss +1 -0
  125. data/client/lanes/styles/mixins/_dropdown.scss +21 -0
  126. data/client/lanes/styles/mixins/_flexbox.scss +394 -0
  127. data/client/lanes/styles/mixins/all.scss +2 -0
  128. data/client/lanes/styles/variables.scss +28 -0
  129. data/client/lanes/testing/BeforeEach.coffee +15 -14
  130. data/client/lanes/testing/Helpers.coffee +14 -5
  131. data/client/lanes/testing/TestObjects.coffee +10 -2
  132. data/client/lanes/testing/index.js +1 -0
  133. data/client/lanes/testing/jasmine-react.js +125 -0
  134. data/client/lanes/vendor/base.js +56049 -74987
  135. data/client/lanes/vendor/calendar.js +17301 -0
  136. data/client/lanes/vendor/calendar.scss +303 -0
  137. data/client/lanes/vendor/commons.js +14990 -15847
  138. data/client/lanes/vendor/development.js +4912 -1952
  139. data/client/lanes/vendor/grid.js +14246 -5551
  140. data/client/lanes/vendor/grid.scss +876 -335
  141. data/client/lanes/vendor/index.js +1 -0
  142. data/client/lanes/vendor/message-bus-ajax.js +44 -0
  143. data/client/lanes/vendor/message-bus.js +414 -0
  144. data/client/lanes/vendor/rw-widgets.eot +0 -0
  145. data/client/lanes/vendor/rw-widgets.svg +18 -0
  146. data/client/lanes/vendor/rw-widgets.ttf +0 -0
  147. data/client/lanes/vendor/rw-widgets.woff +0 -0
  148. data/client/lanes/vendor/toggle.js +345 -0
  149. data/client/lanes/vendor/toggle.scss +138 -0
  150. data/client/lanes/vendor/widgets.js +21245 -6839
  151. data/client/lanes/vendor/widgets.scss +83 -67
  152. data/client/lanes/workspace/Layout.cjsx +18 -8
  153. data/client/lanes/workspace/Modal.cjsx +47 -0
  154. data/client/lanes/workspace/Navbar.cjsx +16 -2
  155. data/client/lanes/workspace/ScreenView.cjsx +10 -3
  156. data/client/lanes/workspace/ScreensMenu.cjsx +23 -7
  157. data/client/lanes/workspace/Tabs.cjsx +55 -0
  158. data/client/lanes/workspace/UIState.coffee +7 -8
  159. data/client/lanes/workspace/index.js +2 -1
  160. data/client/lanes/workspace/mixin.coffee +11 -0
  161. data/client/lanes/workspace/mixins/UIState.coffee +8 -0
  162. data/client/lanes/workspace/mixins/index.js +3 -0
  163. data/client/lanes/workspace/styles.scss +2 -1
  164. data/client/lanes/workspace/styles/header.scss +23 -1
  165. data/client/lanes/workspace/styles/layout.scss +26 -24
  166. data/client/lanes/workspace/styles/screens.scss +0 -4
  167. data/client/lanes/workspace/styles/tabs.scss +3 -10
  168. data/config/routes.rb +10 -4
  169. data/config/screens.rb +25 -0
  170. data/db/migrate/01_create_system_settings.rb +10 -0
  171. data/docs/todo-example-part-1.md +18 -20
  172. data/lanes.gemspec +15 -6
  173. data/lib/lanes.rb +4 -2
  174. data/lib/lanes/access/config/routes.rb +5 -3
  175. data/lib/lanes/access/config/screens.rb +1 -0
  176. data/lib/lanes/access/db/migrate/20140615031600_create_lanes_users.rb +1 -1
  177. data/lib/lanes/access/user.rb +1 -1
  178. data/lib/lanes/api.rb +2 -1
  179. data/lib/lanes/api/controller.rb +32 -71
  180. data/lib/lanes/api/default_routes.rb +10 -8
  181. data/lib/lanes/api/formatted_reply.rb +53 -0
  182. data/lib/lanes/api/handlers/file.rb +26 -0
  183. data/lib/lanes/api/helper_methods.rb +29 -5
  184. data/lib/lanes/api/javascript_processor.rb +36 -17
  185. data/lib/lanes/api/pub_sub.rb +6 -9
  186. data/lib/lanes/api/request_wrapper.rb +1 -2
  187. data/lib/lanes/api/root.rb +11 -43
  188. data/lib/lanes/api/routing.rb +63 -0
  189. data/lib/lanes/api/sprockets_extension.rb +15 -7
  190. data/lib/lanes/api/updates.rb +1 -2
  191. data/lib/lanes/command.rb +0 -1
  192. data/lib/lanes/command/app.rb +6 -5
  193. data/lib/lanes/command/console.rb +1 -0
  194. data/lib/lanes/command/generate.rb +3 -0
  195. data/lib/lanes/command/generate_migration.rb +33 -0
  196. data/lib/lanes/command/generate_model.rb +4 -26
  197. data/lib/lanes/command/migration_support.rb +29 -0
  198. data/lib/lanes/command/update_model.rb +14 -5
  199. data/lib/lanes/concerns/all.rb +2 -0
  200. data/lib/lanes/concerns/api_path.rb +4 -2
  201. data/lib/lanes/concerns/association_extensions.rb +1 -1
  202. data/lib/lanes/concerns/attr_accessor_with_default.rb +3 -1
  203. data/lib/lanes/concerns/code_identifier.rb +1 -1
  204. data/lib/lanes/concerns/image_uploader.rb +42 -0
  205. data/lib/lanes/concerns/pub_sub.rb +0 -1
  206. data/lib/lanes/concerns/queries.rb +2 -2
  207. data/lib/lanes/concerns/set_attribute_data.rb +4 -13
  208. data/lib/lanes/concerns/sorting_expressions.rb +34 -0
  209. data/lib/lanes/configuration.rb +48 -9
  210. data/lib/lanes/extension.rb +16 -7
  211. data/lib/lanes/extension/definition.rb +8 -2
  212. data/lib/lanes/job.rb +78 -0
  213. data/lib/lanes/job/failure_logger.rb +33 -0
  214. data/lib/lanes/model.rb +4 -0
  215. data/lib/lanes/rake_tasks.rb +6 -0
  216. data/lib/lanes/redis.rb +13 -0
  217. data/lib/lanes/screen.rb +34 -18
  218. data/lib/lanes/system_settings.rb +66 -0
  219. data/lib/lanes/version.rb +1 -1
  220. data/lib/lanes/workspace/extension.rb +1 -1
  221. data/npm-build/base.js +10 -3
  222. data/npm-build/calendar.js +6 -0
  223. data/npm-build/development.js +4 -5
  224. data/npm-build/grid.js +3 -5
  225. data/npm-build/package.json +40 -29
  226. data/npm-build/react-toggle.js +5 -0
  227. data/npm-build/react-widgets.js +6 -0
  228. data/npm-build/update-dayz +14 -0
  229. data/npm-build/webpack.config.js +5 -2
  230. data/spec/command-reference-files/initial/Gemfile +1 -1
  231. data/spec/command-reference-files/initial/config/routes.rb +2 -0
  232. data/spec/command-reference-files/initial/lib/appy-app.rb +4 -0
  233. data/spec/command-reference-files/initial/lib/appy-app/extension.rb +2 -0
  234. data/spec/command-reference-files/initial/spec/server/{spec_helpers.rb → spec_helper.rb} +0 -0
  235. data/spec/command-reference-files/model/config/routes.rb +2 -0
  236. data/spec/command-reference-files/model/spec/server/test_test_spec.rb +1 -1
  237. data/spec/command-reference-files/screen/client/appy-app/screens/ready-set-go/ReadySetGo.cjsx +6 -4
  238. data/spec/command-reference-files/screen/client/appy-app/screens/ready-set-go/index.scss +4 -3
  239. data/spec/command-reference-files/screen/config/screens.rb +4 -2
  240. data/spec/command-reference-files/screen/spec/appy-app/screens/ready-set-go/ReadySetGoSpec.coffee +1 -1
  241. data/spec/fixtures/system_settings.yml +1 -0
  242. data/spec/lanes/components/grid/GridSpec.coffee +56 -31
  243. data/spec/lanes/components/grid/RowEditorSpec.coffee +96 -0
  244. data/spec/lanes/components/select-field/SelectFieldSpec.coffee +99 -0
  245. data/spec/lanes/components/shared/NetworkActivityOverlaySpec.coffee +34 -0
  246. data/spec/lanes/models/AssociationMapSpec.coffee +36 -2
  247. data/spec/lanes/models/AssociationProxySpec.coffee +77 -0
  248. data/spec/lanes/models/BaseSpec.coffee +37 -4
  249. data/spec/lanes/models/CollectionSpec.coffee +11 -17
  250. data/spec/lanes/models/PubSubSpec.coffee +1 -1
  251. data/spec/lanes/models/ServerCacheSpec.coffee +65 -0
  252. data/spec/server/api/coffeescript_processor_spec.rb +1 -1
  253. data/spec/server/concerns/pub_sub_spec.rb +9 -10
  254. data/spec/server/concerns/sorting_expressions_spec.rb +34 -0
  255. data/spec/server/configuration_spec.rb +3 -3
  256. data/spec/server/job_spec.rb +54 -0
  257. data/spec/server/spec_helper.rb +0 -5
  258. data/spec/server/system_settings_spec.rb +23 -0
  259. data/templates/client/screens/Screen.cjsx +6 -4
  260. data/templates/client/screens/styles.scss +4 -3
  261. data/templates/config/routes.rb +2 -0
  262. data/templates/config/screen.rb +4 -2
  263. data/templates/lib/namespace.rb +4 -0
  264. data/templates/lib/namespace/extension.rb +2 -0
  265. data/templates/spec/client/Screen.coffee +1 -1
  266. data/templates/spec/server/model_spec.rb +1 -1
  267. data/templates/spec/server/{spec_helpers.rb → spec_helper.rb} +0 -0
  268. data/views/lanes_root_view.erb +70 -0
  269. data/views/specs.erb +2 -2
  270. metadata +207 -68
  271. data/client/images/lanes/dataTables/Sorting icons.psd +0 -0
  272. data/client/images/lanes/dataTables/back_disabled.png +0 -0
  273. data/client/images/lanes/dataTables/back_enabled.png +0 -0
  274. data/client/images/lanes/dataTables/back_enabled_hover.png +0 -0
  275. data/client/images/lanes/dataTables/favicon.ico +0 -0
  276. data/client/images/lanes/dataTables/forward_disabled.png +0 -0
  277. data/client/images/lanes/dataTables/forward_enabled.png +0 -0
  278. data/client/images/lanes/dataTables/forward_enabled_hover.png +0 -0
  279. data/client/images/lanes/dataTables/loading-background.png +0 -0
  280. data/client/images/lanes/dataTables/sort_asc.png +0 -0
  281. data/client/images/lanes/dataTables/sort_asc_disabled.png +0 -0
  282. data/client/images/lanes/dataTables/sort_both.png +0 -0
  283. data/client/images/lanes/dataTables/sort_desc.png +0 -0
  284. data/client/images/lanes/dataTables/sort_desc_disabled.png +0 -0
  285. data/client/lanes/components/shared/Resize.cjsx +0 -152
  286. data/client/lanes/components/shared/TextArea.cjsx +0 -19
  287. data/client/lanes/components/shared/TextField.cjsx +0 -25
  288. data/client/lanes/models/Bootstrap.coffee +0 -5
  289. data/client/lanes/models/QueryResults.coffee +0 -93
  290. data/client/lanes/react/FormBindings.coffee +0 -103
  291. data/client/lanes/react/Router.cjsx +0 -18
  292. data/client/lanes/styles/dataTables.scss +0 -4
  293. data/client/lanes/styles/plugins/all.scss +0 -2
  294. data/client/lanes/styles/plugins/resize-sensor.scss +0 -24
  295. data/client/lanes/vendor/jquery-2.js +0 -9190
  296. data/client/lanes/vendor/jquery.tap.js +0 -401
  297. data/client/lanes/vendor/magicsuggest.js +0 -1565
  298. data/client/lanes/vendor/message-bus.coffee +0 -264
  299. data/client/lanes/workspace/ActiveScreenSwitcher.cjsx +0 -38
  300. data/client/lanes/workspace/styles/toolbar.scss +0 -4
  301. data/lib/lanes/api/eco.js +0 -516
  302. data/lib/lanes/api/sprockets_compressor.rb +0 -39
  303. data/spec/command-reference-files/model/lib/appy-app.rb +0 -11
  304. data/views/index.erb +0 -19
@@ -1,401 +0,0 @@
1
- /**
2
- * @fileOverview
3
- * Copyright (c) 2013 Aaron Gloege
4
- *
5
- * Permission is hereby granted, free of charge, to any person
6
- * obtaining a copy of this software and associated documentation
7
- * files (the "Software"), to deal in the Software without restriction,
8
- * including without limitation the rights to use, copy, modify, merge,
9
- * publish, distribute, sublicense, and/or sell copies of the Software,
10
- * and to permit persons to whom the Software is furnished to do so,
11
- * subject to the following conditions:
12
- *
13
- * The above copyright notice and this permission notice shall be
14
- * included in all copies or substantial portions of the Software.
15
- *
16
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18
- * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20
- * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21
- * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
23
- * OR OTHER DEALINGS IN THE SOFTWARE.
24
- *
25
- * jQuery Tap Plugin
26
- * Using the tap event, this plugin will properly simulate a click event
27
- * in touch browsers using touch events, and on non-touch browsers,
28
- * click will automatically be used instead.
29
- *
30
- * @author Aaron Gloege
31
- * @version 1.1.0
32
- */
33
- (function(document, $) {
34
- 'use strict';
35
-
36
- /**
37
- * Event namespace
38
- *
39
- * @type String
40
- * @final
41
- */
42
- var HELPER_NAMESPACE = '._tap';
43
-
44
- /**
45
- * Event namespace
46
- *
47
- * @type String
48
- * @final
49
- */
50
- var HELPER_ACTIVE_NAMESPACE = '._tapActive';
51
-
52
- /**
53
- * Event name
54
- *
55
- * @type String
56
- * @final
57
- */
58
- var EVENT_NAME = 'tap';
59
-
60
- /**
61
- * Max distance between touchstart and touchend to be considered a tap
62
- *
63
- * @type Number
64
- * @final
65
- */
66
- var MAX_TAP_DELTA = 10;
67
-
68
- /**
69
- * Max duration between touchstart and touchend to be considered a tap
70
- *
71
- * @type Number
72
- * @final
73
- */
74
- var MAX_TAP_TIME = 400;
75
-
76
- /**
77
- * Event variables to copy to touches
78
- *
79
- * @type String[]
80
- * @final
81
- */
82
- var EVENT_VARIABLES = 'clientX clientY screenX screenY pageX pageY'.split(' ');
83
-
84
- /**
85
- * jQuery body object
86
- *
87
- * @type jQuery
88
- */
89
- var $BODY;
90
-
91
- /**
92
- * Last canceled tap event
93
- *
94
- * @type jQuery.Event
95
- * @private
96
- */
97
- var _lastTap;
98
-
99
- /**
100
- * Last touchstart event
101
- *
102
- * @type jQuery.Event
103
- * @private
104
- */
105
- var _lastTouch;
106
-
107
- /**
108
- * Object for tracking current touch
109
- *
110
- * @type Object
111
- * @static
112
- */
113
- var TOUCH_VALUES = {
114
-
115
- /**
116
- * Number of touches currently active on touchstart
117
- *
118
- * @property count
119
- * @type Number
120
- */
121
- count: 0,
122
-
123
- /**
124
- * touchstart/mousedown jQuery.Event object
125
- *
126
- * @property event
127
- * @type jQuery.Event
128
- */
129
- event: 0
130
-
131
- };
132
-
133
- /**
134
- * Create a new event from the original event
135
- * Copy over EVENT_VARIABLES from the original jQuery.Event
136
- *
137
- * @param {String} type
138
- * @param {jQuery.Event} e
139
- * @return {jQuery.Event}
140
- * @private
141
- */
142
- var _createEvent = function(type, e) {
143
- var originalEvent = e.originalEvent;
144
- var event = $.Event(originalEvent);
145
-
146
- event.type = type;
147
-
148
- var i = 0;
149
- var length = EVENT_VARIABLES.length;
150
-
151
- for (; i < length; i++) {
152
- event[EVENT_VARIABLES[i]] = e[EVENT_VARIABLES[i]];
153
- }
154
-
155
- return event;
156
- };
157
-
158
- /**
159
- * Determine if a valid tap event
160
- *
161
- * @param {jQuery.Event} e
162
- * @return {Boolean}
163
- * @private
164
- */
165
- var _isTap = function(e) {
166
- if (e.isTrigger) {
167
- return false;
168
- }
169
-
170
- var startEvent = TOUCH_VALUES.event;
171
- var xDelta = Math.abs(e.pageX - startEvent.pageX);
172
- var yDelta = Math.abs(e.pageY - startEvent.pageY);
173
- var delta = Math.max(xDelta, yDelta);
174
-
175
- return (
176
- e.timeStamp - startEvent.timeStamp < MAX_TAP_TIME &&
177
- delta < MAX_TAP_DELTA &&
178
- (!startEvent.touches || TOUCH_VALUES.count === 1) &&
179
- Tap.isTracking
180
- );
181
- };
182
-
183
- /**
184
- * Determine if mousedown event was emulated from the last touchstart event
185
- *
186
- * @function
187
- * @param {jQuery.Event} e
188
- * @returns {Boolean}
189
- * @private
190
- */
191
- var _isEmulated = function(e) {
192
- if (!_lastTouch) {
193
- return false;
194
- }
195
-
196
- var xDelta = Math.abs(e.pageX - _lastTouch.pageX);
197
- var yDelta = Math.abs(e.pageY - _lastTouch.pageY);
198
- var delta = Math.max(xDelta, yDelta);
199
-
200
- return (
201
- Math.abs(e.timeStamp - _lastTouch.timeStamp) < 750 &&
202
- delta < MAX_TAP_DELTA
203
- );
204
- };
205
-
206
- /**
207
- * Normalize touch events with data from first touch in the jQuery.Event
208
- *
209
- * This could be done using the `jQuery.fixHook` api, but to avoid conflicts
210
- * with other libraries that might already have applied a fix hook, this
211
- * approach is used instead.
212
- *
213
- * @param {jQuery.Event} event
214
- * @private
215
- */
216
- var _normalizeEvent = function(event) {
217
- if (event.type.indexOf('touch') === 0) {
218
- event.touches = event.originalEvent.changedTouches;
219
- var touch = event.touches[0];
220
-
221
- var i = 0;
222
- var length = EVENT_VARIABLES.length;
223
-
224
- for (; i < length; i++) {
225
- event[EVENT_VARIABLES[i]] = touch[EVENT_VARIABLES[i]];
226
- }
227
- }
228
-
229
- // Normalize timestamp
230
- event.timeStamp = Date.now ? Date.now() : +new Date();
231
- };
232
-
233
- /**
234
- * Tap object that will track touch events and
235
- * trigger the tap event when necessary
236
- *
237
- * @class Tap
238
- * @static
239
- */
240
- var Tap = {
241
-
242
- /**
243
- * Flag to determine if touch events are currently enabled
244
- *
245
- * @property isEnabled
246
- * @type Boolean
247
- */
248
- isEnabled: false,
249
-
250
- /**
251
- * Are we currently tracking a tap event?
252
- *
253
- * @property isTracking
254
- * @type Boolean
255
- */
256
- isTracking: false,
257
-
258
- /**
259
- * Enable touch event listeners
260
- *
261
- * @method enable
262
- */
263
- enable: function() {
264
- if (Tap.isEnabled) {
265
- return;
266
- }
267
-
268
- Tap.isEnabled = true;
269
-
270
- // Set body element
271
- $BODY = $(document.body)
272
- .on('touchstart' + HELPER_NAMESPACE, Tap.onStart)
273
- .on('mousedown' + HELPER_NAMESPACE, Tap.onStart)
274
- .on('click' + HELPER_NAMESPACE, Tap.onClick);
275
- },
276
-
277
- /**
278
- * Disable touch event listeners
279
- *
280
- * @method disable
281
- */
282
- disable: function() {
283
- if (!Tap.isEnabled) {
284
- return;
285
- }
286
-
287
- Tap.isEnabled = false;
288
-
289
- // unbind all events with namespace
290
- $BODY.off(HELPER_NAMESPACE);
291
- },
292
-
293
- /**
294
- * Store touch start values and target
295
- *
296
- * @method onTouchStart
297
- * @param {jQuery.Event} e
298
- */
299
- onStart: function(e) {
300
- if (e.isTrigger) {
301
- return;
302
- }
303
-
304
- _normalizeEvent(e);
305
-
306
- if (e.touches) {
307
- TOUCH_VALUES.count = e.touches.length;
308
- }
309
-
310
- if (Tap.isTracking) {
311
- return;
312
- }
313
-
314
- if (!e.touches && _isEmulated(e)) {
315
- return;
316
- }
317
-
318
- Tap.isTracking = true;
319
-
320
- TOUCH_VALUES.event = e;
321
-
322
- if (e.touches) {
323
- _lastTouch = e;
324
- $BODY
325
- .on('touchend' + HELPER_NAMESPACE + HELPER_ACTIVE_NAMESPACE, Tap.onEnd)
326
- .on('touchcancel' + HELPER_NAMESPACE + HELPER_ACTIVE_NAMESPACE, Tap.onCancel);
327
- } else {
328
- $BODY.on('mouseup' + HELPER_NAMESPACE + HELPER_ACTIVE_NAMESPACE, Tap.onEnd);
329
- }
330
- },
331
-
332
- /**
333
- * If touch has not been canceled, create a
334
- * tap event and trigger it on the target element
335
- *
336
- * @method onTouchEnd
337
- * @param {jQuery.Event} e
338
- */
339
- onEnd: function(e) {
340
- var event;
341
-
342
- if (e.isTrigger) {
343
- return;
344
- }
345
-
346
- _normalizeEvent(e);
347
-
348
- if (_isTap(e)) {
349
- event = _createEvent(EVENT_NAME, e);
350
- _lastTap = event;
351
- $(TOUCH_VALUES.event.target).trigger(event);
352
- }
353
-
354
- // Cancel active tap tracking
355
- Tap.onCancel(e);
356
- },
357
-
358
- /**
359
- * Cancel tap and remove event listeners for active tap tracking
360
- *
361
- * @method onTouchCancel
362
- * @param {jQuery.Event} e
363
- */
364
- onCancel: function(e) {
365
- if (e && e.type === 'touchcancel') {
366
- e.preventDefault();
367
- }
368
-
369
- Tap.isTracking = false;
370
-
371
- $BODY.off(HELPER_ACTIVE_NAMESPACE);
372
- },
373
-
374
- /**
375
- * If tap was canceled, cancel click event
376
- *
377
- * @method onClick
378
- * @param {jQuery.Event} e
379
- * @return {void|Boolean}
380
- */
381
- onClick: function(e) {
382
- if (
383
- !e.isTrigger &&
384
- _lastTap &&
385
- _lastTap.isDefaultPrevented() &&
386
- _lastTap.target === e.target &&
387
- _lastTap.pageX === e.pageX &&
388
- _lastTap.pageY === e.pageY &&
389
- e.timeStamp - _lastTap.timeStamp < 750
390
- ) {
391
- _lastTap = null;
392
- return false;
393
- }
394
- }
395
-
396
- };
397
-
398
- // Enable tab when document is ready
399
- $(document).ready(Tap.enable);
400
-
401
- }(document, jQuery));
@@ -1,1565 +0,0 @@
1
- /**
2
- * Multiple Selection Component for Bootstrap
3
- * Check nicolasbize.github.io/magicsuggest/ for latest updates.
4
- *
5
- * Author: Nicolas Bize
6
- * Created: Feb 8th 2013
7
- * Last Updated: Oct 16th 2014
8
- * Version: 2.1.4
9
- * Licence: MagicSuggest is licenced under MIT licence (http://opensource.org/licenses/MIT)
10
- */
11
- (function($)
12
- {
13
- "use strict";
14
- var MagicSuggest = function(element, options)
15
- {
16
- var ms = this;
17
-
18
- /**
19
- * Initializes the MagicSuggest component
20
- */
21
- var defaults = {
22
- /********** CONFIGURATION PROPERTIES ************/
23
- /**
24
- * Restricts or allows the user to validate typed entries.
25
- * Defaults to true.
26
- */
27
- allowFreeEntries: true,
28
-
29
- /**
30
- * Restricts or allows the user to add the same entry more than once
31
- * Defaults to false.
32
- */
33
- allowDuplicates: false,
34
-
35
- /**
36
- * Additional config object passed to each $.ajax call
37
- */
38
- ajaxConfig: {},
39
-
40
- /**
41
- * If a single suggestion comes out, it is preselected.
42
- */
43
- autoSelect: true,
44
-
45
- /**
46
- * Auto select the first matching item with multiple items shown
47
- */
48
- selectFirst: false,
49
-
50
- /**
51
- * Allow customization of query parameter
52
- */
53
- queryParam: 'query',
54
-
55
- /**
56
- * A function triggered just before the ajax request is sent, similar to jQuery
57
- */
58
- beforeSend: function(){ },
59
-
60
- /**
61
- * A custom CSS class to apply to the field's underlying element.
62
- */
63
- cls: '',
64
-
65
- /**
66
- * JSON Data source used to populate the combo box. 3 options are available here:
67
- * No Data Source (default)
68
- * When left null, the combo box will not suggest anything. It can still enable the user to enter
69
- * multiple entries if allowFreeEntries is * set to true (default).
70
- * Static Source
71
- * You can pass an array of JSON objects, an array of strings or even a single CSV string as the
72
- * data source.For ex. data: [* {id:0,name:"Paris"}, {id: 1, name: "New York"}]
73
- * You can also pass any json object with the results property containing the json array.
74
- * Url
75
- * You can pass the url from which the component will fetch its JSON data.Data will be fetched
76
- * using a POST ajax request that will * include the entered text as 'query' parameter. The results
77
- * fetched from the server can be:
78
- * - an array of JSON objects (ex: [{id:...,name:...},{...}])
79
- * - a string containing an array of JSON objects ready to be parsed (ex: "[{id:...,name:...},{...}]")
80
- * - a JSON object whose data will be contained in the results property
81
- * (ex: {results: [{id:...,name:...},{...}]
82
- * Function
83
- * You can pass a function which returns an array of JSON objects (ex: [{id:...,name:...},{...}])
84
- * The function can return the JSON data or it can use the first argument as function to handle the data.
85
- * Only one (callback function or return value) is needed for the function to succeed.
86
- * See the following example:
87
- * function (response) { var myjson = [{name: 'test', id: 1}]; response(myjson); return myjson; }
88
- */
89
- data: null,
90
-
91
- /**
92
- * Additional parameters to the ajax call
93
- */
94
- dataUrlParams: {},
95
-
96
- /**
97
- * Start the component in a disabled state.
98
- */
99
- disabled: false,
100
-
101
- /**
102
- * Name of JSON object property that defines the disabled behaviour
103
- */
104
- disabledField: null,
105
-
106
- /**
107
- * Name of JSON object property displayed in the combo list
108
- */
109
- displayField: 'name',
110
-
111
- /**
112
- * Set to false if you only want mouse interaction. In that case the combo will
113
- * automatically expand on focus.
114
- */
115
- editable: true,
116
-
117
- /**
118
- * Set starting state for combo.
119
- */
120
- expanded: false,
121
-
122
- /**
123
- * Automatically expands combo on focus.
124
- */
125
- expandOnFocus: false,
126
-
127
- /**
128
- * JSON property by which the list should be grouped
129
- */
130
- groupBy: null,
131
-
132
- /**
133
- * Set to true to hide the trigger on the right
134
- */
135
- hideTrigger: false,
136
-
137
- /**
138
- * Set to true to highlight search input within displayed suggestions
139
- */
140
- highlight: true,
141
-
142
- /**
143
- * A custom ID for this component
144
- */
145
- id: null,
146
-
147
- /**
148
- * A class that is added to the info message appearing on the top-right part of the component
149
- */
150
- infoMsgCls: '',
151
-
152
- /**
153
- * Additional parameters passed out to the INPUT tag. Enables usage of AngularJS's custom tags for ex.
154
- */
155
- inputCfg: {},
156
-
157
- /**
158
- * The class that is applied to show that the field is invalid
159
- */
160
- invalidCls: 'ms-inv',
161
-
162
- /**
163
- * Set to true to filter data results according to case. Useless if the data is fetched remotely
164
- */
165
- matchCase: false,
166
-
167
- /**
168
- * Once expanded, the combo's height will take as much room as the # of available results.
169
- * In case there are too many results displayed, this will fix the drop down height.
170
- */
171
- maxDropHeight: 290,
172
-
173
- /**
174
- * Defines how long the user free entry can be. Set to null for no limit.
175
- */
176
- maxEntryLength: null,
177
-
178
- /**
179
- * A function that defines the helper text when the max entry length has been surpassed.
180
- */
181
- maxEntryRenderer: function(v) {
182
- return 'Please reduce your entry by ' + v + ' character' + (v > 1 ? 's':'');
183
- },
184
-
185
- /**
186
- * The maximum number of results displayed in the combo drop down at once.
187
- */
188
- maxSuggestions: null,
189
-
190
- /**
191
- * The maximum number of items the user can select if multiple selection is allowed.
192
- * Set to null to remove the limit.
193
- */
194
- maxSelection: 10,
195
-
196
- /**
197
- * A function that defines the helper text when the max selection amount has been reached. The function has a single
198
- * parameter which is the number of selected elements.
199
- */
200
- maxSelectionRenderer: function(v) {
201
- return 'You cannot choose more than ' + v + ' item' + (v > 1 ? 's':'');
202
- },
203
-
204
- /**
205
- * The method used by the ajax request.
206
- */
207
- method: 'POST',
208
-
209
- /**
210
- * The minimum number of characters the user must type before the combo expands and offers suggestions.
211
- */
212
- minChars: 0,
213
-
214
- /**
215
- * A function that defines the helper text when not enough letters are set. The function has a single
216
- * parameter which is the difference between the required amount of letters and the current one.
217
- */
218
- minCharsRenderer: function(v) {
219
- return 'Please type ' + v + ' more character' + (v > 1 ? 's':'');
220
- },
221
-
222
- /**
223
- * Whether or not sorting / filtering should be done remotely or locally.
224
- * Use either 'local' or 'remote'
225
- */
226
- mode: 'local',
227
-
228
- /**
229
- * The name used as a form element.
230
- */
231
- name: null,
232
-
233
- /**
234
- * The text displayed when there are no suggestions.
235
- */
236
- noSuggestionText: 'No suggestions',
237
-
238
- /**
239
- * The default placeholder text when nothing has been entered
240
- */
241
- placeholder: 'Type or click here',
242
-
243
- /**
244
- * A function used to define how the items will be presented in the combo
245
- */
246
- renderer: null,
247
-
248
- /**
249
- * Whether or not this field should be required
250
- */
251
- required: false,
252
-
253
- /**
254
- * Set to true to render selection as a delimited string
255
- */
256
- resultAsString: false,
257
-
258
- /**
259
- * Text delimiter to use in a delimited string.
260
- */
261
- resultAsStringDelimiter: ',',
262
-
263
- /**
264
- * Name of JSON object property that represents the list of suggested objects
265
- */
266
- resultsField: 'results',
267
-
268
- /**
269
- * A custom CSS class to add to a selected item
270
- */
271
- selectionCls: '',
272
-
273
- /**
274
- * An optional element replacement in which the selection is rendered
275
- */
276
- selectionContainer: null,
277
-
278
- /**
279
- * Where the selected items will be displayed. Only 'right', 'bottom' and 'inner' are valid values
280
- */
281
- selectionPosition: 'inner',
282
-
283
- /**
284
- * A function used to define how the items will be presented in the tag list
285
- */
286
- selectionRenderer: null,
287
-
288
- /**
289
- * Set to true to stack the selectioned items when positioned on the bottom
290
- * Requires the selectionPosition to be set to 'bottom'
291
- */
292
- selectionStacked: false,
293
-
294
- /**
295
- * Direction used for sorting. Only 'asc' and 'desc' are valid values
296
- */
297
- sortDir: 'asc',
298
-
299
- /**
300
- * name of JSON object property for local result sorting.
301
- * Leave null if you do not wish the results to be ordered or if they are already ordered remotely.
302
- */
303
- sortOrder: null,
304
-
305
- /**
306
- * If set to true, suggestions will have to start by user input (and not simply contain it as a substring)
307
- */
308
- strictSuggest: false,
309
-
310
- /**
311
- * Custom style added to the component container.
312
- */
313
- style: '',
314
-
315
- /**
316
- * If set to true, the combo will expand / collapse when clicked upon
317
- */
318
- toggleOnClick: false,
319
-
320
-
321
- /**
322
- * Amount (in ms) between keyboard registers.
323
- */
324
- typeDelay: 400,
325
-
326
- /**
327
- * If set to true, tab won't blur the component but will be registered as the ENTER key
328
- */
329
- useTabKey: false,
330
-
331
- /**
332
- * If set to true, using comma will validate the user's choice
333
- */
334
- useCommaKey: true,
335
-
336
-
337
- /**
338
- * Determines whether or not the results will be displayed with a zebra table style
339
- */
340
- useZebraStyle: false,
341
-
342
- /**
343
- * initial value for the field
344
- */
345
- value: null,
346
-
347
- /**
348
- * name of JSON object property that represents its underlying value
349
- */
350
- valueField: 'id',
351
-
352
- /**
353
- * regular expression to validate the values against
354
- */
355
- vregex: null,
356
-
357
- /**
358
- * type to validate against
359
- */
360
- vtype: null
361
- };
362
-
363
- var conf = $.extend({},options);
364
- var cfg = $.extend(true, {}, defaults, conf);
365
-
366
- /********** PUBLIC METHODS ************/
367
- /**
368
- * Add one or multiple json items to the current selection
369
- * @param items - json object or array of json objects
370
- * @param isSilent - (optional) set to true to suppress 'selectionchange' event from being triggered
371
- */
372
- this.addToSelection = function(items, isSilent)
373
- {
374
- if (!cfg.maxSelection || _selection.length < cfg.maxSelection) {
375
- if (!$.isArray(items)) {
376
- items = [items];
377
- }
378
- var valuechanged = false;
379
- $.each(items, function(index, json) {
380
- if (cfg.allowDuplicates || $.inArray(json[cfg.valueField], ms.getValue()) === -1) {
381
- _selection.push(json);
382
- valuechanged = true;
383
- }
384
- });
385
- if(valuechanged === true) {
386
- self._renderSelection();
387
- this.empty();
388
- if (isSilent !== true) {
389
- $(this).trigger('selectionchange', [this, this.getSelection()]);
390
- }
391
- }
392
- }
393
- this.input.attr('placeholder', (cfg.selectionPosition === 'inner' && this.getValue().length > 0) ? '' : cfg.placeholder);
394
- };
395
-
396
- /**
397
- * Clears the current selection
398
- * @param isSilent - (optional) set to true to suppress 'selectionchange' event from being triggered
399
- */
400
- this.clear = function(isSilent)
401
- {
402
- this.removeFromSelection(_selection.slice(0), isSilent); // clone array to avoid concurrency issues
403
- };
404
-
405
- /**
406
- * Collapse the drop down part of the combo
407
- */
408
- this.collapse = function()
409
- {
410
- if (cfg.expanded === true) {
411
- this.combobox.detach();
412
- cfg.expanded = false;
413
- $(this).trigger('collapse', [this]);
414
- }
415
- };
416
-
417
- /**
418
- * Set the component in a disabled state.
419
- */
420
- this.disable = function()
421
- {
422
- this.container.addClass('ms-ctn-disabled');
423
- cfg.disabled = true;
424
- ms.input.attr('disabled', true);
425
- };
426
-
427
- /**
428
- * Empties out the combo user text
429
- */
430
- this.empty = function(){
431
- this.input.val('');
432
- };
433
-
434
- /**
435
- * Set the component in a enable state.
436
- */
437
- this.enable = function()
438
- {
439
- this.container.removeClass('ms-ctn-disabled');
440
- cfg.disabled = false;
441
- ms.input.attr('disabled', false);
442
- };
443
-
444
- /**
445
- * Expand the drop drown part of the combo.
446
- */
447
- this.expand = function()
448
- {
449
- if (!cfg.expanded && (this.input.val().length >= cfg.minChars || this.combobox.children().size() > 0)) {
450
- this.combobox.appendTo(this.container);
451
- self._processSuggestions();
452
- cfg.expanded = true;
453
- $(this).trigger('expand', [this]);
454
- }
455
- };
456
-
457
- /**
458
- * Retrieve component enabled status
459
- */
460
- this.isDisabled = function()
461
- {
462
- return cfg.disabled;
463
- };
464
-
465
- /**
466
- * Checks whether the field is valid or not
467
- * @return {boolean}
468
- */
469
- this.isValid = function()
470
- {
471
- var valid = cfg.required === false || _selection.length > 0;
472
- if(cfg.vtype || cfg.vregex){
473
- $.each(_selection, function(index, item){
474
- valid = valid && self._validateSingleItem(item[cfg.valueField]);
475
- });
476
- }
477
- return valid;
478
- };
479
-
480
- /**
481
- * Gets the data params for current ajax request
482
- */
483
- this.getDataUrlParams = function()
484
- {
485
- return cfg.dataUrlParams;
486
- };
487
-
488
- /**
489
- * Gets the name given to the form input
490
- */
491
- this.getName = function()
492
- {
493
- return cfg.name;
494
- };
495
-
496
- /**
497
- * Retrieve an array of selected json objects
498
- * @return {Array}
499
- */
500
- this.getSelection = function()
501
- {
502
- return _selection;
503
- };
504
-
505
- /**
506
- * Retrieve the current text entered by the user
507
- */
508
- this.getRawValue = function(){
509
- return ms.input.val();
510
- };
511
-
512
- /**
513
- * Retrieve an array of selected values
514
- */
515
- this.getValue = function()
516
- {
517
- return $.map(_selection, function(o) {
518
- return o[cfg.valueField];
519
- });
520
- };
521
-
522
- /**
523
- * Remove one or multiples json items from the current selection
524
- * @param items - json object or array of json objects
525
- * @param isSilent - (optional) set to true to suppress 'selectionchange' event from being triggered
526
- */
527
- this.removeFromSelection = function(items, isSilent)
528
- {
529
- if (!$.isArray(items)) {
530
- items = [items];
531
- }
532
- var valuechanged = false;
533
- $.each(items, function(index, json) {
534
- var i = $.inArray(json[cfg.valueField], ms.getValue());
535
- if (i > -1) {
536
- _selection.splice(i, 1);
537
- valuechanged = true;
538
- }
539
- });
540
- if (valuechanged === true) {
541
- self._renderSelection();
542
- if(isSilent !== true){
543
- $(this).trigger('selectionchange', [this, this.getSelection()]);
544
- }
545
- if(cfg.expandOnFocus){
546
- ms.expand();
547
- }
548
- if(cfg.expanded) {
549
- self._processSuggestions();
550
- }
551
- }
552
- this.input.attr('placeholder', (cfg.selectionPosition === 'inner' && this.getValue().length > 0) ? '' : cfg.placeholder);
553
- };
554
-
555
- /**
556
- * Get current data
557
- */
558
- this.getData = function(){
559
- return _cbData;
560
- };
561
-
562
- /**
563
- * Set up some combo data after it has been rendered
564
- * @param data
565
- */
566
- this.setData = function(data){
567
- cfg.data = data;
568
- self._processSuggestions();
569
- };
570
-
571
- /**
572
- * Sets the name for the input field so it can be fetched in the form
573
- * @param name
574
- */
575
- this.setName = function(name){
576
- cfg.name = name;
577
- if(name){
578
- cfg.name += name.indexOf('[]') > 0 ? '' : '[]';
579
- }
580
- if(ms._valueContainer){
581
- $.each(ms._valueContainer.children(), function(i, el){
582
- el.name = cfg.name;
583
- });
584
- }
585
- };
586
-
587
- /**
588
- * Sets the current selection with the JSON items provided
589
- * @param items
590
- */
591
- this.setSelection = function(items){
592
- this.clear();
593
- this.addToSelection(items);
594
- };
595
-
596
- /**
597
- * Sets a value for the combo box. Value must be an array of values with data type matching valueField one.
598
- * @param data
599
- */
600
- this.setValue = function(values)
601
- {
602
- var items = [];
603
-
604
- $.each(values, function(index, value) {
605
- // first try to see if we have the full objects from our data set
606
- var found = false;
607
- $.each(_cbData, function(i,item){
608
- if(item[cfg.valueField] == value){
609
- items.push(item);
610
- found = true;
611
- return false;
612
- }
613
- });
614
- if(!found){
615
- if(typeof(value) === 'object'){
616
- items.push(value);
617
- } else {
618
- var json = {};
619
- json[cfg.valueField] = value;
620
- json[cfg.displayField] = value;
621
- items.push(json);
622
- }
623
- }
624
- });
625
- if(items.length > 0) {
626
- this.addToSelection(items);
627
- }
628
- };
629
-
630
- /**
631
- * Sets data params for subsequent ajax requests
632
- * @param params
633
- */
634
- this.setDataUrlParams = function(params)
635
- {
636
- cfg.dataUrlParams = $.extend({},params);
637
- };
638
-
639
- /********** PRIVATE ************/
640
- var _selection = [], // selected objects
641
- _comboItemHeight = 0, // height for each combo item.
642
- _timer,
643
- _hasFocus = false,
644
- _groups = null,
645
- _cbData = [],
646
- _ctrlDown = false,
647
- KEYCODES = {
648
- BACKSPACE: 8,
649
- TAB: 9,
650
- ENTER: 13,
651
- CTRL: 17,
652
- ESC: 27,
653
- SPACE: 32,
654
- UPARROW: 38,
655
- DOWNARROW: 40,
656
- COMMA: 188
657
- };
658
-
659
- var self = {
660
-
661
- /**
662
- * Empties the result container and refills it with the array of json results in input
663
- * @private
664
- */
665
- _displaySuggestions: function(data) {
666
- ms.combobox.show();
667
- ms.combobox.empty();
668
-
669
- var resHeight = 0, // total height taken by displayed results.
670
- nbGroups = 0;
671
-
672
- if(_groups === null) {
673
- self._renderComboItems(data);
674
- resHeight = _comboItemHeight * data.length;
675
- }
676
- else {
677
- for(var grpName in _groups) {
678
- nbGroups += 1;
679
- $('<div/>', {
680
- 'class': 'ms-res-group',
681
- html: grpName
682
- }).appendTo(ms.combobox);
683
- self._renderComboItems(_groups[grpName].items, true);
684
- }
685
- var _groupItemHeight = ms.combobox.find('.ms-res-group').outerHeight();
686
- if(_groupItemHeight !== null) {
687
- var tmpResHeight = nbGroups * _groupItemHeight;
688
- resHeight = (_comboItemHeight * data.length) + tmpResHeight;
689
- } else {
690
- resHeight = _comboItemHeight * (data.length + nbGroups);
691
- }
692
- }
693
-
694
- if(resHeight < ms.combobox.height() || resHeight <= cfg.maxDropHeight) {
695
- ms.combobox.height(resHeight);
696
- }
697
- else if(resHeight >= ms.combobox.height() && resHeight > cfg.maxDropHeight) {
698
- ms.combobox.height(cfg.maxDropHeight);
699
- }
700
-
701
- if(data.length === 1 && cfg.autoSelect === true) {
702
- ms.combobox.children().filter(':not(.ms-res-item-disabled):last').addClass('ms-res-item-active');
703
- }
704
-
705
- if (cfg.selectFirst === true) {
706
- ms.combobox.children().filter(':not(.ms-res-item-disabled):first').addClass('ms-res-item-active');
707
- }
708
-
709
- if(data.length === 0 && ms.getRawValue() !== "") {
710
- var noSuggestionText = cfg.noSuggestionText.replace(/\{\{.*\}\}/, ms.input.val());
711
- self._updateHelper(noSuggestionText);
712
- ms.collapse();
713
- }
714
-
715
- // When free entry is off, add invalid class to input if no data matches
716
- if(cfg.allowFreeEntries === false) {
717
- if(data.length === 0) {
718
- $(ms.input).addClass(cfg.invalidCls);
719
- ms.combobox.hide();
720
- } else {
721
- $(ms.input).removeClass(cfg.invalidCls);
722
- }
723
- }
724
- },
725
-
726
- /**
727
- * Returns an array of json objects from an array of strings.
728
- * @private
729
- */
730
- _getEntriesFromStringArray: function(data) {
731
- var json = [];
732
- $.each(data, function(index, s) {
733
- var entry = {};
734
- entry[cfg.displayField] = entry[cfg.valueField] = $.trim(s);
735
- json.push(entry);
736
- });
737
- return json;
738
- },
739
-
740
- /**
741
- * Replaces html with highlighted html according to case
742
- * @param html
743
- * @private
744
- */
745
- _highlightSuggestion: function(html) {
746
- var q = ms.input.val();
747
-
748
- //escape special regex characters
749
- var specialCharacters = ['^', '$', '*', '+', '?', '.', '(', ')', ':', '!', '|', '{', '}', '[', ']'];
750
-
751
- $.each(specialCharacters, function (index, value) {
752
- q = q.replace(value, "\\" + value);
753
- })
754
-
755
- if(q.length === 0) {
756
- return html; // nothing entered as input
757
- }
758
-
759
- var glob = cfg.matchCase === true ? 'g' : 'gi';
760
- return html.replace(new RegExp('(' + q + ')(?!([^<]+)?>)', glob), '<em>$1</em>');
761
- },
762
-
763
- /**
764
- * Moves the selected cursor amongst the list item
765
- * @param dir - 'up' or 'down'
766
- * @private
767
- */
768
- _moveSelectedRow: function(dir) {
769
- if(!cfg.expanded) {
770
- ms.expand();
771
- }
772
- var list, start, active, scrollPos;
773
- list = ms.combobox.find(".ms-res-item:not(.ms-res-item-disabled)");
774
- if(dir === 'down') {
775
- start = list.eq(0);
776
- }
777
- else {
778
- start = list.filter(':last');
779
- }
780
- active = ms.combobox.find('.ms-res-item-active:not(.ms-res-item-disabled):first');
781
- if(active.length > 0) {
782
- if(dir === 'down') {
783
- start = active.nextAll('.ms-res-item:not(.ms-res-item-disabled)').first();
784
- if(start.length === 0) {
785
- start = list.eq(0);
786
- }
787
- scrollPos = ms.combobox.scrollTop();
788
- ms.combobox.scrollTop(0);
789
- if(start[0].offsetTop + start.outerHeight() > ms.combobox.height()) {
790
- ms.combobox.scrollTop(scrollPos + _comboItemHeight);
791
- }
792
- }
793
- else {
794
- start = active.prevAll('.ms-res-item:not(.ms-res-item-disabled)').first();
795
- if(start.length === 0) {
796
- start = list.filter(':last');
797
- ms.combobox.scrollTop(_comboItemHeight * list.length);
798
- }
799
- if(start[0].offsetTop < ms.combobox.scrollTop()) {
800
- ms.combobox.scrollTop(ms.combobox.scrollTop() - _comboItemHeight);
801
- }
802
- }
803
- }
804
- list.removeClass("ms-res-item-active");
805
- start.addClass("ms-res-item-active");
806
- },
807
-
808
- /**
809
- * According to given data and query, sort and add suggestions in their container
810
- * @private
811
- */
812
- _processSuggestions: function(source) {
813
- var json = null, data = source || cfg.data;
814
- if(data !== null) {
815
- if(typeof(data) === 'function'){
816
- data = data.call(ms, ms.getRawValue());
817
- }
818
- if(typeof(data) === 'string') { // get results from ajax
819
- $(ms).trigger('beforeload', [ms]);
820
- var queryParams = {}
821
- queryParams[cfg.queryParam] = ms.input.val();
822
- var params = $.extend(queryParams, cfg.dataUrlParams);
823
- $.ajax($.extend({
824
- type: cfg.method,
825
- url: data,
826
- data: params,
827
- beforeSend: cfg.beforeSend,
828
- success: function(asyncData){
829
- json = typeof(asyncData) === 'string' ? JSON.parse(asyncData) : asyncData;
830
- self._processSuggestions(json);
831
- $(ms).trigger('load', [ms, json]);
832
- if(self._asyncValues){
833
- ms.setValue(typeof(self._asyncValues) === 'string' ? JSON.parse(self._asyncValues) : self._asyncValues);
834
- self._renderSelection();
835
- delete(self._asyncValues);
836
- }
837
- },
838
- error: function(){
839
- throw("Could not reach server");
840
- }
841
- }, cfg.ajaxConfig));
842
- return;
843
- } else { // results from local array
844
- if(data.length > 0 && typeof(data[0]) === 'string') { // results from array of strings
845
- _cbData = self._getEntriesFromStringArray(data);
846
- } else { // regular json array or json object with results property
847
- _cbData = data[cfg.resultsField] || data;
848
- }
849
- }
850
- var sortedData = cfg.mode === 'remote' ? _cbData : self._sortAndTrim(_cbData);
851
- self._displaySuggestions(self._group(sortedData));
852
-
853
- }
854
- },
855
-
856
- /**
857
- * Render the component to the given input DOM element
858
- * @private
859
- */
860
- _render: function(el) {
861
- ms.setName(cfg.name); // make sure the form name is correct
862
- // holds the main div, will relay the focus events to the contained input element.
863
- ms.container = $('<div/>', {
864
- 'class': 'ms-ctn form-control ' + (cfg.resultAsString ? 'ms-as-string ' : '') + cfg.cls +
865
- ($(el).hasClass('input-lg') ? ' input-lg' : '') +
866
- ($(el).hasClass('input-sm') ? ' input-sm' : '') +
867
- (cfg.disabled === true ? ' ms-ctn-disabled' : '') +
868
- (cfg.editable === true ? '' : ' ms-ctn-readonly') +
869
- (cfg.hideTrigger === false ? '' : ' ms-no-trigger'),
870
- style: cfg.style,
871
- id: cfg.id
872
- });
873
- ms.container.focus($.proxy(handlers._onFocus, this));
874
- ms.container.blur($.proxy(handlers._onBlur, this));
875
- ms.container.keydown($.proxy(handlers._onKeyDown, this));
876
- ms.container.keyup($.proxy(handlers._onKeyUp, this));
877
-
878
- // holds the input field
879
- ms.input = $('<input/>', $.extend({
880
- type: 'text',
881
- 'class': cfg.editable === true ? '' : ' ms-input-readonly',
882
- readonly: !cfg.editable,
883
- placeholder: cfg.placeholder,
884
- disabled: cfg.disabled
885
- }, cfg.inputCfg));
886
-
887
- ms.input.focus($.proxy(handlers._onInputFocus, this));
888
- ms.input.click($.proxy(handlers._onInputClick, this));
889
-
890
- // holds the suggestions. will always be placed on focus
891
- ms.combobox = $('<div/>', {
892
- 'class': 'ms-res-ctn dropdown-menu'
893
- }).height(cfg.maxDropHeight);
894
-
895
- // bind the onclick and mouseover using delegated events (needs jQuery >= 1.7)
896
- ms.combobox.on('click', 'div.ms-res-item', $.proxy(handlers._onComboItemSelected, this));
897
- ms.combobox.on('mouseover', 'div.ms-res-item', $.proxy(handlers._onComboItemMouseOver, this));
898
-
899
- if(cfg.selectionContainer){
900
- ms.selectionContainer = cfg.selectionContainer;
901
- $(ms.selectionContainer).addClass('ms-sel-ctn');
902
- } else {
903
- ms.selectionContainer = $('<div/>', {
904
- 'class': 'ms-sel-ctn'
905
- });
906
- }
907
- ms.selectionContainer.click($.proxy(handlers._onFocus, this));
908
-
909
- if(cfg.selectionPosition === 'inner' && !cfg.selectionContainer) {
910
- ms.selectionContainer.append(ms.input);
911
- }
912
- else {
913
- ms.container.append(ms.input);
914
- }
915
-
916
- ms.helper = $('<span/>', {
917
- 'class': 'ms-helper ' + cfg.infoMsgCls
918
- });
919
- self._updateHelper();
920
- ms.container.append(ms.helper);
921
-
922
-
923
- // Render the whole thing
924
- $(el).replaceWith(ms.container);
925
-
926
- if(!cfg.selectionContainer){
927
- switch(cfg.selectionPosition) {
928
- case 'bottom':
929
- ms.selectionContainer.insertAfter(ms.container);
930
- if(cfg.selectionStacked === true) {
931
- ms.selectionContainer.width(ms.container.width());
932
- ms.selectionContainer.addClass('ms-stacked');
933
- }
934
- break;
935
- case 'right':
936
- ms.selectionContainer.insertAfter(ms.container);
937
- ms.container.css('float', 'left');
938
- break;
939
- default:
940
- ms.container.append(ms.selectionContainer);
941
- break;
942
- }
943
- }
944
-
945
-
946
- // holds the trigger on the right side
947
- if(cfg.hideTrigger === false) {
948
- ms.trigger = $('<div/>', {
949
- 'class': 'ms-trigger',
950
- html: '<div class="ms-trigger-ico"></div>'
951
- });
952
- ms.trigger.click($.proxy(handlers._onTriggerClick, this));
953
- ms.container.append(ms.trigger);
954
- }
955
-
956
- $(window).resize($.proxy(handlers._onWindowResized, this));
957
-
958
- // do not perform an initial call if we are using ajax unless we have initial values
959
- if(cfg.value !== null || cfg.data !== null){
960
- if(typeof(cfg.data) === 'string'){
961
- self._asyncValues = cfg.value;
962
- self._processSuggestions();
963
- } else {
964
- self._processSuggestions();
965
- if(cfg.value !== null){
966
- ms.setValue(cfg.value);
967
- self._renderSelection();
968
- }
969
- }
970
-
971
- }
972
-
973
- $("body").click(function(e) {
974
- if(ms.container.hasClass('ms-ctn-focus') &&
975
- ms.container.has(e.target).length === 0 &&
976
- e.target.className.indexOf('ms-res-item') < 0 &&
977
- e.target.className.indexOf('ms-close-btn') < 0 &&
978
- ms.container[0] !== e.target) {
979
- handlers._onBlur();
980
- }
981
- });
982
-
983
- if(cfg.expanded === true) {
984
- cfg.expanded = false;
985
- ms.expand();
986
- }
987
- },
988
-
989
- /**
990
- * Renders each element within the combo box
991
- * @private
992
- */
993
- _renderComboItems: function(items, isGrouped) {
994
- var ref = this, html = '';
995
- $.each(items, function(index, value) {
996
- var displayed = cfg.renderer !== null ? cfg.renderer.call(ref, value) : value[cfg.displayField];
997
- var disabled = cfg.disabledField !== null && value[cfg.disabledField] === true;
998
- var resultItemEl = $('<div/>', {
999
- 'class': 'ms-res-item ' + (isGrouped ? 'ms-res-item-grouped ':'') +
1000
- (disabled ? 'ms-res-item-disabled ':'') +
1001
- (index % 2 === 1 && cfg.useZebraStyle === true ? 'ms-res-odd' : ''),
1002
- html: cfg.highlight === true ? self._highlightSuggestion(displayed) : displayed,
1003
- 'data-json': JSON.stringify(value)
1004
- });
1005
- html += $('<div/>').append(resultItemEl).html();
1006
- });
1007
- ms.combobox.append(html);
1008
- _comboItemHeight = ms.combobox.find('.ms-res-item:first').outerHeight();
1009
- },
1010
-
1011
- /**
1012
- * Renders the selected items into their container.
1013
- * @private
1014
- */
1015
- _renderSelection: function() {
1016
- var ref = this, w = 0, inputOffset = 0, items = [],
1017
- asText = cfg.resultAsString === true && !_hasFocus;
1018
-
1019
- ms.selectionContainer.find('.ms-sel-item').remove();
1020
- if(ms._valueContainer !== undefined) {
1021
- ms._valueContainer.remove();
1022
- }
1023
-
1024
- $.each(_selection, function(index, value){
1025
-
1026
- var selectedItemEl, delItemEl,
1027
- selectedItemHtml = cfg.selectionRenderer !== null ? cfg.selectionRenderer.call(ref, value) : value[cfg.displayField];
1028
-
1029
- var validCls = self._validateSingleItem(value[cfg.displayField]) ? '' : ' ms-sel-invalid';
1030
-
1031
- // tag representing selected value
1032
- if(asText === true) {
1033
- selectedItemEl = $('<div/>', {
1034
- 'class': 'ms-sel-item ms-sel-text ' + cfg.selectionCls + validCls,
1035
- html: selectedItemHtml + (index === (_selection.length - 1) ? '' : cfg.resultAsStringDelimiter)
1036
- }).data('json', value);
1037
- }
1038
- else {
1039
- selectedItemEl = $('<div/>', {
1040
- 'class': 'ms-sel-item ' + cfg.selectionCls + validCls,
1041
- html: selectedItemHtml
1042
- }).data('json', value);
1043
-
1044
- if(cfg.disabled === false){
1045
- // small cross img
1046
- delItemEl = $('<span/>', {
1047
- 'class': 'ms-close-btn'
1048
- }).data('json', value).appendTo(selectedItemEl);
1049
-
1050
- delItemEl.click($.proxy(handlers._onTagTriggerClick, ref));
1051
- }
1052
- }
1053
-
1054
- items.push(selectedItemEl);
1055
- });
1056
- ms.selectionContainer.prepend(items);
1057
-
1058
- // store the values, behaviour of multiple select
1059
- ms._valueContainer = $('<div/>', {
1060
- style: 'display: none;'
1061
- });
1062
- $.each(ms.getValue(), function(i, val){
1063
- var el = $('<input/>', {
1064
- type: 'hidden',
1065
- name: cfg.name,
1066
- value: val
1067
- });
1068
- el.appendTo(ms._valueContainer);
1069
- });
1070
- ms._valueContainer.appendTo(ms.selectionContainer);
1071
-
1072
- if(cfg.selectionPosition === 'inner' && !cfg.selectionContainer) {
1073
- ms.input.width(0);
1074
- inputOffset = ms.input.offset().left - ms.selectionContainer.offset().left;
1075
- w = ms.container.width() - inputOffset - 42;
1076
- ms.input.width(w);
1077
- }
1078
-
1079
- if(_selection.length === cfg.maxSelection){
1080
- self._updateHelper(cfg.maxSelectionRenderer.call(this, _selection.length));
1081
- } else {
1082
- ms.helper.hide();
1083
- }
1084
- },
1085
-
1086
- /**
1087
- * Select an item either through keyboard or mouse
1088
- * @param item
1089
- * @private
1090
- */
1091
- _selectItem: function(item) {
1092
- if(cfg.maxSelection === 1){
1093
- _selection = [];
1094
- }
1095
- ms.addToSelection(item.data('json'));
1096
- item.removeClass('ms-res-item-active');
1097
- if(cfg.expandOnFocus === false || _selection.length === cfg.maxSelection){
1098
- ms.collapse();
1099
- }
1100
- if(!_hasFocus){
1101
- ms.input.focus();
1102
- } else if(_hasFocus && (cfg.expandOnFocus || _ctrlDown)){
1103
- self._processSuggestions();
1104
- if(_ctrlDown){
1105
- ms.expand();
1106
- }
1107
- }
1108
- },
1109
-
1110
- /**
1111
- * Sorts the results and cut them down to max # of displayed results at once
1112
- * @private
1113
- */
1114
- _sortAndTrim: function(data) {
1115
- var q = ms.getRawValue(),
1116
- filtered = [],
1117
- newSuggestions = [],
1118
- selectedValues = ms.getValue();
1119
- // filter the data according to given input
1120
- if(q.length > 0) {
1121
- $.each(data, function(index, obj) {
1122
- var name = obj[cfg.displayField];
1123
- if((cfg.matchCase === true && name.indexOf(q) > -1) ||
1124
- (cfg.matchCase === false && name.toLowerCase().indexOf(q.toLowerCase()) > -1)) {
1125
- if(cfg.strictSuggest === false || name.toLowerCase().indexOf(q.toLowerCase()) === 0) {
1126
- filtered.push(obj);
1127
- }
1128
- }
1129
- });
1130
- }
1131
- else {
1132
- filtered = data;
1133
- }
1134
- // take out the ones that have already been selected
1135
- $.each(filtered, function(index, obj) {
1136
- if (cfg.allowDuplicates || $.inArray(obj[cfg.valueField], selectedValues) === -1) {
1137
- newSuggestions.push(obj);
1138
- }
1139
- });
1140
- // sort the data
1141
- if(cfg.sortOrder !== null) {
1142
- newSuggestions.sort(function(a,b) {
1143
- if(a[cfg.sortOrder] < b[cfg.sortOrder]) {
1144
- return cfg.sortDir === 'asc' ? -1 : 1;
1145
- }
1146
- if(a[cfg.sortOrder] > b[cfg.sortOrder]) {
1147
- return cfg.sortDir === 'asc' ? 1 : -1;
1148
- }
1149
- return 0;
1150
- });
1151
- }
1152
- // trim it down
1153
- if(cfg.maxSuggestions && cfg.maxSuggestions > 0) {
1154
- newSuggestions = newSuggestions.slice(0, cfg.maxSuggestions);
1155
- }
1156
- return newSuggestions;
1157
-
1158
- },
1159
-
1160
- _group: function(data){
1161
- // build groups
1162
- if(cfg.groupBy !== null) {
1163
- _groups = {};
1164
-
1165
- $.each(data, function(index, value) {
1166
- var props = cfg.groupBy.indexOf('.') > -1 ? cfg.groupBy.split('.') : cfg.groupBy;
1167
- var prop = value[cfg.groupBy];
1168
- if(typeof(props) != 'string'){
1169
- prop = value;
1170
- while(props.length > 0){
1171
- prop = prop[props.shift()];
1172
- }
1173
- }
1174
- if(_groups[prop] === undefined) {
1175
- _groups[prop] = {title: prop, items: [value]};
1176
- }
1177
- else {
1178
- _groups[prop].items.push(value);
1179
- }
1180
- });
1181
- }
1182
- return data;
1183
- },
1184
-
1185
- /**
1186
- * Update the helper text
1187
- * @private
1188
- */
1189
- _updateHelper: function(html) {
1190
- ms.helper.html(html);
1191
- if(!ms.helper.is(":visible")) {
1192
- ms.helper.fadeIn();
1193
- }
1194
- },
1195
-
1196
- /**
1197
- * Validate an item against vtype or vregex
1198
- * @private
1199
- */
1200
- _validateSingleItem: function(value){
1201
- if(cfg.vregex !== null && cfg.vregex instanceof RegExp){
1202
- return cfg.vregex.test(value);
1203
- } else if(cfg.vtype !== null) {
1204
- switch(cfg.vtype){
1205
- case 'alpha':
1206
- return (/^[a-zA-Z_]+$/).test(value);
1207
- case 'alphanum':
1208
- return (/^[a-zA-Z0-9_]+$/).test(value);
1209
- case 'email':
1210
- return (/^(\w+)([\-+.][\w]+)*@(\w[\-\w]*\.){1,5}([A-Za-z]){2,6}$/).test(value);
1211
- case 'url':
1212
- return (/(((^https?)|(^ftp)):\/\/([\-\w]+\.)+\w{2,3}(\/[%\-\w]+(\.\w{2,})?)*(([\w\-\.\?\\\/+@&#;`~=%!]*)(\.\w{2,})?)*\/?)/i).test(value);
1213
- case 'ipaddress':
1214
- return (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/).test(value);
1215
- }
1216
- }
1217
- return true;
1218
- }
1219
- };
1220
-
1221
- var handlers = {
1222
- /**
1223
- * Triggered when blurring out of the component
1224
- * @private
1225
- */
1226
- _onBlur: function() {
1227
- ms.container.removeClass('ms-ctn-focus');
1228
- ms.collapse();
1229
- _hasFocus = false;
1230
- if(ms.getRawValue() !== '' && cfg.allowFreeEntries === true){
1231
- var obj = {};
1232
- obj[cfg.displayField] = obj[cfg.valueField] = ms.getRawValue().trim();
1233
- ms.addToSelection(obj);
1234
- }
1235
- self._renderSelection();
1236
-
1237
- if(ms.isValid() === false) {
1238
- ms.container.addClass(cfg.invalidCls);
1239
- }
1240
-
1241
- else if(ms.input.val() !== '' && cfg.allowFreeEntries === false) {
1242
- ms.empty();
1243
- self._updateHelper('');
1244
- }
1245
-
1246
- $(ms).trigger('blur', [ms]);
1247
- },
1248
-
1249
- /**
1250
- * Triggered when hovering an element in the combo
1251
- * @param e
1252
- * @private
1253
- */
1254
- _onComboItemMouseOver: function(e) {
1255
- var target = $(e.currentTarget);
1256
- if(!target.hasClass('ms-res-item-disabled')){
1257
- ms.combobox.children().removeClass('ms-res-item-active');
1258
- target.addClass('ms-res-item-active');
1259
- }
1260
- },
1261
-
1262
- /**
1263
- * Triggered when an item is chosen from the list
1264
- * @param e
1265
- * @private
1266
- */
1267
- _onComboItemSelected: function(e) {
1268
- var target = $(e.currentTarget);
1269
- if(!target.hasClass('ms-res-item-disabled')){
1270
- self._selectItem($(e.currentTarget));
1271
- }
1272
- },
1273
-
1274
- /**
1275
- * Triggered when focusing on the container div. Will focus on the input field instead.
1276
- * @private
1277
- */
1278
- _onFocus: function() {
1279
- ms.input.focus();
1280
- },
1281
-
1282
- /**
1283
- * Triggered when clicking on the input text field
1284
- * @private
1285
- */
1286
- _onInputClick: function(){
1287
- if (ms.isDisabled() === false && _hasFocus) {
1288
- if (cfg.toggleOnClick === true) {
1289
- if (cfg.expanded){
1290
- ms.collapse();
1291
- } else {
1292
- ms.expand();
1293
- }
1294
- }
1295
- }
1296
- },
1297
-
1298
- /**
1299
- * Triggered when focusing on the input text field.
1300
- * @private
1301
- */
1302
- _onInputFocus: function() {
1303
- if(ms.isDisabled() === false && !_hasFocus) {
1304
- _hasFocus = true;
1305
- ms.container.addClass('ms-ctn-focus');
1306
- ms.container.removeClass(cfg.invalidCls);
1307
-
1308
- var curLength = ms.getRawValue().length;
1309
- if(cfg.expandOnFocus === true){
1310
- ms.expand();
1311
- }
1312
-
1313
- if(_selection.length === cfg.maxSelection) {
1314
- self._updateHelper(cfg.maxSelectionRenderer.call(this, _selection.length));
1315
- } else if(curLength < cfg.minChars) {
1316
- self._updateHelper(cfg.minCharsRenderer.call(this, cfg.minChars - curLength));
1317
- }
1318
-
1319
- self._renderSelection();
1320
- $(ms).trigger('focus', [ms]);
1321
- }
1322
- },
1323
-
1324
- /**
1325
- * Triggered when the user presses a key while the component has focus
1326
- * This is where we want to handle all keys that don't require the user input field
1327
- * since it hasn't registered the key hit yet
1328
- * @param e keyEvent
1329
- * @private
1330
- */
1331
- _onKeyDown: function(e) {
1332
- // check how tab should be handled
1333
- var active = ms.combobox.find('.ms-res-item-active:not(.ms-res-item-disabled):first'),
1334
- freeInput = ms.input.val();
1335
- $(ms).trigger('keydown', [ms, e]);
1336
-
1337
- if(e.keyCode === KEYCODES.TAB && (cfg.useTabKey === false ||
1338
- (cfg.useTabKey === true && active.length === 0 && ms.input.val().length === 0))) {
1339
- handlers._onBlur();
1340
- return;
1341
- }
1342
- switch(e.keyCode) {
1343
- case KEYCODES.BACKSPACE:
1344
- if(freeInput.length === 0 && ms.getSelection().length > 0 && cfg.selectionPosition === 'inner') {
1345
- _selection.pop();
1346
- self._renderSelection();
1347
- $(ms).trigger('selectionchange', [ms, ms.getSelection()]);
1348
- ms.input.attr('placeholder', (cfg.selectionPosition === 'inner' && ms.getValue().length > 0) ? '' : cfg.placeholder);
1349
- ms.input.focus();
1350
- e.preventDefault();
1351
- }
1352
- break;
1353
- case KEYCODES.TAB:
1354
- case KEYCODES.ESC:
1355
- e.preventDefault();
1356
- break;
1357
- case KEYCODES.ENTER:
1358
- if(freeInput !== '' || cfg.expanded){
1359
- e.preventDefault();
1360
- }
1361
- break;
1362
- case KEYCODES.COMMA:
1363
- if(cfg.useCommaKey === true){
1364
- e.preventDefault();
1365
- }
1366
- break;
1367
- case KEYCODES.CTRL:
1368
- _ctrlDown = true;
1369
- break;
1370
- case KEYCODES.DOWNARROW:
1371
- e.preventDefault();
1372
- self._moveSelectedRow("down");
1373
- break;
1374
- case KEYCODES.UPARROW:
1375
- e.preventDefault();
1376
- self._moveSelectedRow("up");
1377
- break;
1378
- default:
1379
- if(_selection.length === cfg.maxSelection) {
1380
- e.preventDefault();
1381
- }
1382
- break;
1383
- }
1384
- },
1385
-
1386
- /**
1387
- * Triggered when a key is released while the component has focus
1388
- * @param e
1389
- * @private
1390
- */
1391
- _onKeyUp: function(e) {
1392
- var freeInput = ms.getRawValue(),
1393
- inputValid = $.trim(ms.input.val()).length > 0 &&
1394
- (!cfg.maxEntryLength || $.trim(ms.input.val()).length <= cfg.maxEntryLength),
1395
- selected,
1396
- obj = {};
1397
-
1398
- $(ms).trigger('keyup', [ms, e]);
1399
-
1400
- clearTimeout(_timer);
1401
-
1402
- // collapse if escape, but keep focus.
1403
- if(e.keyCode === KEYCODES.ESC && cfg.expanded) {
1404
- ms.combobox.hide();
1405
- }
1406
- // ignore a bunch of keys
1407
- if((e.keyCode === KEYCODES.TAB && cfg.useTabKey === false) || (e.keyCode > KEYCODES.ENTER && e.keyCode < KEYCODES.SPACE)) {
1408
- if(e.keyCode === KEYCODES.CTRL){
1409
- _ctrlDown = false;
1410
- }
1411
- return;
1412
- }
1413
- switch(e.keyCode) {
1414
- case KEYCODES.UPARROW:
1415
- case KEYCODES.DOWNARROW:
1416
- e.preventDefault();
1417
- break;
1418
- case KEYCODES.ENTER:
1419
- case KEYCODES.TAB:
1420
- case KEYCODES.COMMA:
1421
- if(e.keyCode !== KEYCODES.COMMA || cfg.useCommaKey === true) {
1422
- e.preventDefault();
1423
- if(cfg.expanded === true){ // if a selection is performed, select it and reset field
1424
- selected = ms.combobox.find('.ms-res-item-active:not(.ms-res-item-disabled):first');
1425
- if(selected.length > 0) {
1426
- self._selectItem(selected);
1427
- return;
1428
- }
1429
- }
1430
- // if no selection or if freetext entered and free entries allowed, add new obj to selection
1431
- if(inputValid === true && cfg.allowFreeEntries === true) {
1432
- obj[cfg.displayField] = obj[cfg.valueField] = freeInput.trim();
1433
- ms.addToSelection(obj);
1434
- ms.collapse(); // reset combo suggestions
1435
- ms.input.focus();
1436
- }
1437
- break;
1438
- }
1439
- default:
1440
- if(_selection.length === cfg.maxSelection){
1441
- self._updateHelper(cfg.maxSelectionRenderer.call(this, _selection.length));
1442
- }
1443
- else {
1444
- if(freeInput.length < cfg.minChars) {
1445
- self._updateHelper(cfg.minCharsRenderer.call(this, cfg.minChars - freeInput.length));
1446
- if(cfg.expanded === true) {
1447
- ms.collapse();
1448
- }
1449
- }
1450
- else if(cfg.maxEntryLength && freeInput.length > cfg.maxEntryLength) {
1451
- self._updateHelper(cfg.maxEntryRenderer.call(this, freeInput.length - cfg.maxEntryLength));
1452
- if(cfg.expanded === true) {
1453
- ms.collapse();
1454
- }
1455
- }
1456
- else {
1457
- ms.helper.hide();
1458
- if(cfg.minChars <= freeInput.length){
1459
- _timer = setTimeout(function() {
1460
- if(cfg.expanded === true) {
1461
- self._processSuggestions();
1462
- } else {
1463
- ms.expand();
1464
- }
1465
- }, cfg.typeDelay);
1466
- }
1467
- }
1468
- }
1469
- break;
1470
- }
1471
- },
1472
-
1473
- /**
1474
- * Triggered when clicking upon cross for deletion
1475
- * @param e
1476
- * @private
1477
- */
1478
- _onTagTriggerClick: function(e) {
1479
- ms.removeFromSelection($(e.currentTarget).data('json'));
1480
- },
1481
-
1482
- /**
1483
- * Triggered when clicking on the small trigger in the right
1484
- * @private
1485
- */
1486
- _onTriggerClick: function() {
1487
- if(ms.isDisabled() === false && !(cfg.expandOnFocus === true && _selection.length === cfg.maxSelection)) {
1488
- $(ms).trigger('triggerclick', [ms]);
1489
- if(cfg.expanded === true) {
1490
- ms.collapse();
1491
- } else {
1492
- var curLength = ms.getRawValue().length;
1493
- if(curLength >= cfg.minChars){
1494
- ms.input.focus();
1495
- ms.expand();
1496
- } else {
1497
- self._updateHelper(cfg.minCharsRenderer.call(this, cfg.minChars - curLength));
1498
- }
1499
- }
1500
- }
1501
- },
1502
-
1503
- /**
1504
- * Triggered when the browser window is resized
1505
- * @private
1506
- */
1507
- _onWindowResized: function() {
1508
- self._renderSelection();
1509
- }
1510
- };
1511
-
1512
- // startup point
1513
- if(element !== null) {
1514
- self._render(element);
1515
- }
1516
- };
1517
-
1518
- $.fn.magicSuggest = function(options) {
1519
- var obj = $(this);
1520
-
1521
- if(obj.size() === 1 && obj.data('magicSuggest')) {
1522
- return obj.data('magicSuggest');
1523
- }
1524
-
1525
- obj.each(function(i) {
1526
- // assume $(this) is an element
1527
- var cntr = $(this);
1528
-
1529
- // Return early if this element already has a plugin instance
1530
- if(cntr.data('magicSuggest')){
1531
- return;
1532
- }
1533
-
1534
- if(this.nodeName.toLowerCase() === 'select'){ // rendering from select
1535
- options.data = [];
1536
- options.value = [];
1537
- $.each(this.children, function(index, child){
1538
- if(child.nodeName && child.nodeName.toLowerCase() === 'option'){
1539
- options.data.push({id: child.value, name: child.text});
1540
- if($(child).attr('selected')){
1541
- options.value.push(child.value);
1542
- }
1543
- }
1544
- });
1545
- }
1546
-
1547
- var def = {};
1548
- // set values from DOM container element
1549
- $.each(this.attributes, function(i, att){
1550
- def[att.name] = att.name === 'value' && att.value !== '' ? JSON.parse(att.value) : att.value;
1551
- });
1552
-
1553
- var field = new MagicSuggest(this, $.extend([], $.fn.magicSuggest.defaults, options, def));
1554
- cntr.data('magicSuggest', field);
1555
- field.container.data('magicSuggest', field);
1556
- });
1557
-
1558
- if(obj.size() === 1) {
1559
- return obj.data('magicSuggest');
1560
- }
1561
- return obj;
1562
- };
1563
-
1564
- $.fn.magicSuggest.defaults = {};
1565
- })(jQuery);