adapt-authoring-ui 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (414) hide show
  1. package/.eslintignore +2 -0
  2. package/.eslintrc +14 -0
  3. package/.github/ISSUE_TEMPLATE/bug_report.yml +55 -0
  4. package/.github/ISSUE_TEMPLATE/config.yml +1 -0
  5. package/.github/ISSUE_TEMPLATE/feature_request.yml +22 -0
  6. package/.github/dependabot.yml +11 -0
  7. package/.github/pull_request_template.md +25 -0
  8. package/.github/workflows/labelled_prs.yml +16 -0
  9. package/.github/workflows/new.yml +19 -0
  10. package/adapt-authoring.json +11 -0
  11. package/app/core/app.js +43 -0
  12. package/app/core/assets/adapt-learning-logo-outline.png +0 -0
  13. package/app/core/assets/adapt-learning-logo-white.png +0 -0
  14. package/app/core/assets/ajax-loader.gif +0 -0
  15. package/app/core/assets/favicon.png +0 -0
  16. package/app/core/assets/transparency-light.png +0 -0
  17. package/app/core/browserStorage.js +45 -0
  18. package/app/core/collections/apiCollection.js +74 -0
  19. package/app/core/collections/contentCollection.js +33 -0
  20. package/app/core/collections/contentPluginCollection.js +24 -0
  21. package/app/core/collections/tagsCollection.js +4 -0
  22. package/app/core/constants.js +10 -0
  23. package/app/core/helpers.js +375 -0
  24. package/app/core/index.hbs +192 -0
  25. package/app/core/l10n.js +33 -0
  26. package/app/core/less/_reset.less +43 -0
  27. package/app/core/less/app.less +100 -0
  28. package/app/core/less/buttons.less +268 -0
  29. package/app/core/less/colourLabels.less +14 -0
  30. package/app/core/less/colours.less +132 -0
  31. package/app/core/less/columns.less +190 -0
  32. package/app/core/less/fonts.less +30 -0
  33. package/app/core/less/forms.less +77 -0
  34. package/app/core/less/sharedStyles.less +73 -0
  35. package/app/core/less/tables.less +29 -0
  36. package/app/core/less/tags.less +194 -0
  37. package/app/core/loading.hbs +27 -0
  38. package/app/core/models/articleModel.js +19 -0
  39. package/app/core/models/blockModel.js +25 -0
  40. package/app/core/models/clipboardModel.js +10 -0
  41. package/app/core/models/componentModel.js +18 -0
  42. package/app/core/models/configModel.js +22 -0
  43. package/app/core/models/contentModel.js +26 -0
  44. package/app/core/models/contentObjectModel.js +19 -0
  45. package/app/core/models/contentPluginModel.js +10 -0
  46. package/app/core/models/courseModel.js +24 -0
  47. package/app/core/models/sessionModel.js +86 -0
  48. package/app/core/origin.js +189 -0
  49. package/app/core/router.js +135 -0
  50. package/app/core/views/originView.js +92 -0
  51. package/app/libraries/babel-polyfill.min.js +1 -0
  52. package/app/libraries/backbone-forms-lists.js +669 -0
  53. package/app/libraries/backbone-forms.js +2604 -0
  54. package/app/libraries/backbone.js +1581 -0
  55. package/app/libraries/ckeditor.js +6 -0
  56. package/app/libraries/handlebars.js +4902 -0
  57. package/app/libraries/imageReady.js +2 -0
  58. package/app/libraries/inview.js +140 -0
  59. package/app/libraries/jquery-ui.min.js +6 -0
  60. package/app/libraries/jquery.form.js +1277 -0
  61. package/app/libraries/jquery.js +10346 -0
  62. package/app/libraries/jquery.tagsinput.min.js +1 -0
  63. package/app/libraries/moment.min.js +7 -0
  64. package/app/libraries/polyglot.min.js +2922 -0
  65. package/app/libraries/require.js +36 -0
  66. package/app/libraries/scrollTo.js +11 -0
  67. package/app/libraries/selectize/css/selectize.less +403 -0
  68. package/app/libraries/selectize/js/selectize.js +4500 -0
  69. package/app/libraries/spectrum/spectrum.js +2323 -0
  70. package/app/libraries/spectrum/spectrum.less +507 -0
  71. package/app/libraries/underscore.js +1692 -0
  72. package/app/libraries/velocity.js +4773 -0
  73. package/app/modules/appHeader/assets/top-bar-1080.jpg +0 -0
  74. package/app/modules/appHeader/assets/top-bar-2560.jpg +0 -0
  75. package/app/modules/appHeader/index.js +5 -0
  76. package/app/modules/appHeader/less/appHeader.less +72 -0
  77. package/app/modules/appHeader/templates/appHeader.hbs +17 -0
  78. package/app/modules/appHeader/views/appHeaderView.js +63 -0
  79. package/app/modules/assetManagement/collections/assetCollection.js +16 -0
  80. package/app/modules/assetManagement/index.js +65 -0
  81. package/app/modules/assetManagement/less/asset.less +229 -0
  82. package/app/modules/assetManagement/less/assetManagementModalTags.less +34 -0
  83. package/app/modules/assetManagement/less/assetManagementNew.less +60 -0
  84. package/app/modules/assetManagement/models/assetModel.js +11 -0
  85. package/app/modules/assetManagement/templates/assetManagement.hbs +12 -0
  86. package/app/modules/assetManagement/templates/assetManagementCollection.hbs +2 -0
  87. package/app/modules/assetManagement/templates/assetManagementEditAsset.hbs +9 -0
  88. package/app/modules/assetManagement/templates/assetManagementEditAssetSidebar.hbs +10 -0
  89. package/app/modules/assetManagement/templates/assetManagementListItem.hbs +35 -0
  90. package/app/modules/assetManagement/templates/assetManagementModalAutofill.hbs +1 -0
  91. package/app/modules/assetManagement/templates/assetManagementModalEditAsset.hbs +11 -0
  92. package/app/modules/assetManagement/templates/assetManagementModalFilters.hbs +16 -0
  93. package/app/modules/assetManagement/templates/assetManagementModalTags.hbs +26 -0
  94. package/app/modules/assetManagement/templates/assetManagementPreview.hbs +53 -0
  95. package/app/modules/assetManagement/templates/assetManagementSidebar.hbs +93 -0
  96. package/app/modules/assetManagement/templates/assetPicker.hbs +45 -0
  97. package/app/modules/assetManagement/views/assetManagementCollectionView.js +216 -0
  98. package/app/modules/assetManagement/views/assetManagementEditAssetSidebarView.js +25 -0
  99. package/app/modules/assetManagement/views/assetManagementEditAssetView.js +123 -0
  100. package/app/modules/assetManagement/views/assetManagementItemView.js +68 -0
  101. package/app/modules/assetManagement/views/assetManagementModalAutofillView.js +51 -0
  102. package/app/modules/assetManagement/views/assetManagementModalEditAssetView.js +38 -0
  103. package/app/modules/assetManagement/views/assetManagementModalFiltersView.js +132 -0
  104. package/app/modules/assetManagement/views/assetManagementModalTagsView.js +169 -0
  105. package/app/modules/assetManagement/views/assetManagementModalView.js +62 -0
  106. package/app/modules/assetManagement/views/assetManagementPreviewView.js +69 -0
  107. package/app/modules/assetManagement/views/assetManagementSidebarView.js +126 -0
  108. package/app/modules/assetManagement/views/assetManagementView.js +43 -0
  109. package/app/modules/colorLabel/index.js +48 -0
  110. package/app/modules/colorLabel/less/colorLabels.less +102 -0
  111. package/app/modules/colorLabel/models/colors.js +31 -0
  112. package/app/modules/colorLabel/templates/colorLabelPopUpView.hbs +21 -0
  113. package/app/modules/colorLabel/views/colorLabelPopupView.js +75 -0
  114. package/app/modules/contentHeader/index.js +6 -0
  115. package/app/modules/contentHeader/less/contentHeader.less +152 -0
  116. package/app/modules/contentHeader/less/options.less +51 -0
  117. package/app/modules/contentHeader/templates/actionsButton.hbs +3 -0
  118. package/app/modules/contentHeader/templates/contentHeader.hbs +38 -0
  119. package/app/modules/contentHeader/templates/contentHeaderToggleButton.hbs +11 -0
  120. package/app/modules/contentHeader/templates/filterItem.hbs +23 -0
  121. package/app/modules/contentHeader/templates/options.hbs +14 -0
  122. package/app/modules/contentHeader/templates/partials/part_headerButtonContent.hbs +2 -0
  123. package/app/modules/contentHeader/templates/sortItem.hbs +9 -0
  124. package/app/modules/contentHeader/views/actionsButtonView.js +14 -0
  125. package/app/modules/contentHeader/views/contentHeaderButtonView.js +42 -0
  126. package/app/modules/contentHeader/views/contentHeaderToggleButtonView.js +52 -0
  127. package/app/modules/contentHeader/views/contentHeaderView.js +96 -0
  128. package/app/modules/contentHeader/views/filtersButtonView.js +31 -0
  129. package/app/modules/contentHeader/views/optionsView.js +88 -0
  130. package/app/modules/contentHeader/views/sortsButtonView.js +45 -0
  131. package/app/modules/contentPane/index.js +20 -0
  132. package/app/modules/contentPane/less/contentPane.less +8 -0
  133. package/app/modules/contentPane/templates/contentPane.hbs +2 -0
  134. package/app/modules/contentPane/views/contentPaneView.js +77 -0
  135. package/app/modules/contextMenu/index.js +85 -0
  136. package/app/modules/contextMenu/less/contextMenu.less +54 -0
  137. package/app/modules/contextMenu/templates/contextMenu.hbs +6 -0
  138. package/app/modules/contextMenu/templates/contextMenuItem.hbs +8 -0
  139. package/app/modules/contextMenu/views/contextMenuItemView.js +37 -0
  140. package/app/modules/contextMenu/views/contextMenuView.js +76 -0
  141. package/app/modules/editor/article/index.js +18 -0
  142. package/app/modules/editor/article/templates/editorArticleEdit.hbs +2 -0
  143. package/app/modules/editor/article/templates/editorArticleEditSidebar.hbs +19 -0
  144. package/app/modules/editor/article/views/editorArticleEditSidebarView.js +32 -0
  145. package/app/modules/editor/article/views/editorArticleEditView.js +19 -0
  146. package/app/modules/editor/block/index.js +18 -0
  147. package/app/modules/editor/block/templates/editorBlockEdit.hbs +2 -0
  148. package/app/modules/editor/block/templates/editorBlockEditSidebar.hbs +19 -0
  149. package/app/modules/editor/block/views/editorBlockEditSidebarView.js +28 -0
  150. package/app/modules/editor/block/views/editorBlockEditView.js +19 -0
  151. package/app/modules/editor/component/index.js +22 -0
  152. package/app/modules/editor/component/templates/editorComponentEdit.hbs +2 -0
  153. package/app/modules/editor/component/templates/editorComponentEditSidebar.hbs +19 -0
  154. package/app/modules/editor/component/views/editorComponentEditSidebarView.js +32 -0
  155. package/app/modules/editor/component/views/editorComponentEditView.js +23 -0
  156. package/app/modules/editor/config/index.js +15 -0
  157. package/app/modules/editor/config/templates/editorConfigEdit.hbs +1 -0
  158. package/app/modules/editor/config/templates/editorConfigEditSidebar.hbs +10 -0
  159. package/app/modules/editor/config/views/editorConfigEditSidebarView.js +28 -0
  160. package/app/modules/editor/config/views/editorConfigEditView.js +28 -0
  161. package/app/modules/editor/contentObject/assets/component-icons/icon-accordion.png +0 -0
  162. package/app/modules/editor/contentObject/assets/component-icons/icon-assessment.png +0 -0
  163. package/app/modules/editor/contentObject/assets/component-icons/icon-blank.png +0 -0
  164. package/app/modules/editor/contentObject/assets/component-icons/icon-default.png +0 -0
  165. package/app/modules/editor/contentObject/assets/component-icons/icon-gmcq.png +0 -0
  166. package/app/modules/editor/contentObject/assets/component-icons/icon-graphic.png +0 -0
  167. package/app/modules/editor/contentObject/assets/component-icons/icon-hot-graphic.png +0 -0
  168. package/app/modules/editor/contentObject/assets/component-icons/icon-matching.png +0 -0
  169. package/app/modules/editor/contentObject/assets/component-icons/icon-mcq.png +0 -0
  170. package/app/modules/editor/contentObject/assets/component-icons/icon-media.png +0 -0
  171. package/app/modules/editor/contentObject/assets/component-icons/icon-narrative.png +0 -0
  172. package/app/modules/editor/contentObject/assets/component-icons/icon-slider.png +0 -0
  173. package/app/modules/editor/contentObject/assets/component-icons/icon-text.png +0 -0
  174. package/app/modules/editor/contentObject/index.js +68 -0
  175. package/app/modules/editor/contentObject/less/editing-overlay-component-list.less +60 -0
  176. package/app/modules/editor/contentObject/less/editing-overlay-panel.less +25 -0
  177. package/app/modules/editor/contentObject/less/editorMenu.less +84 -0
  178. package/app/modules/editor/contentObject/less/editorMenuItem.less +114 -0
  179. package/app/modules/editor/contentObject/less/editorMenuLayer.less +70 -0
  180. package/app/modules/editor/contentObject/less/editorPage.less +162 -0
  181. package/app/modules/editor/contentObject/less/editorPageArticle.less +40 -0
  182. package/app/modules/editor/contentObject/less/editorPageBlock.less +33 -0
  183. package/app/modules/editor/contentObject/less/editorPageComponent.less +47 -0
  184. package/app/modules/editor/contentObject/less/editorPageComponentList.less +234 -0
  185. package/app/modules/editor/contentObject/templates/editorMenu.hbs +1 -0
  186. package/app/modules/editor/contentObject/templates/editorMenuItem.hbs +23 -0
  187. package/app/modules/editor/contentObject/templates/editorMenuLayer.hbs +12 -0
  188. package/app/modules/editor/contentObject/templates/editorMenuSidebar.hbs +1 -0
  189. package/app/modules/editor/contentObject/templates/editorPage.hbs +15 -0
  190. package/app/modules/editor/contentObject/templates/editorPageArticle.hbs +15 -0
  191. package/app/modules/editor/contentObject/templates/editorPageBlock.hbs +14 -0
  192. package/app/modules/editor/contentObject/templates/editorPageComponent.hbs +9 -0
  193. package/app/modules/editor/contentObject/templates/editorPageComponentList.hbs +33 -0
  194. package/app/modules/editor/contentObject/templates/editorPageComponentListItem.hbs +24 -0
  195. package/app/modules/editor/contentObject/templates/editorPageComponentPasteZone.hbs +4 -0
  196. package/app/modules/editor/contentObject/templates/editorPageEdit.hbs +1 -0
  197. package/app/modules/editor/contentObject/templates/editorPageEditSidebar.hbs +10 -0
  198. package/app/modules/editor/contentObject/templates/editorPageSidebar.hbs +1 -0
  199. package/app/modules/editor/contentObject/views/editorMenuItemView.js +174 -0
  200. package/app/modules/editor/contentObject/views/editorMenuLayerView.js +212 -0
  201. package/app/modules/editor/contentObject/views/editorMenuSidebarView.js +13 -0
  202. package/app/modules/editor/contentObject/views/editorMenuView.js +211 -0
  203. package/app/modules/editor/contentObject/views/editorPageArticleView.js +278 -0
  204. package/app/modules/editor/contentObject/views/editorPageBlockView.js +280 -0
  205. package/app/modules/editor/contentObject/views/editorPageComponentListItemView.js +80 -0
  206. package/app/modules/editor/contentObject/views/editorPageComponentListView.js +131 -0
  207. package/app/modules/editor/contentObject/views/editorPageComponentPasteZoneView.js +47 -0
  208. package/app/modules/editor/contentObject/views/editorPageComponentView.js +202 -0
  209. package/app/modules/editor/contentObject/views/editorPageEditSidebarView.js +28 -0
  210. package/app/modules/editor/contentObject/views/editorPageEditView.js +19 -0
  211. package/app/modules/editor/contentObject/views/editorPageSidebarView.js +13 -0
  212. package/app/modules/editor/contentObject/views/editorPageView.js +183 -0
  213. package/app/modules/editor/course/index.js +26 -0
  214. package/app/modules/editor/course/templates/editorCourseEdit.hbs +2 -0
  215. package/app/modules/editor/course/templates/editorCourseEditSidebar.hbs +19 -0
  216. package/app/modules/editor/course/views/editorCourseEditSidebarView.js +28 -0
  217. package/app/modules/editor/course/views/editorCourseEditView.js +56 -0
  218. package/app/modules/editor/extensions/index.js +26 -0
  219. package/app/modules/editor/extensions/less/extensions.less +22 -0
  220. package/app/modules/editor/extensions/templates/editorExtensionsEdit.hbs +85 -0
  221. package/app/modules/editor/extensions/templates/editorExtensionsEditSidebar.hbs +1 -0
  222. package/app/modules/editor/extensions/views/editorExtensionsEditSidebarView.js +10 -0
  223. package/app/modules/editor/extensions/views/editorExtensionsEditView.js +85 -0
  224. package/app/modules/editor/global/collections/editorCollection.js +24 -0
  225. package/app/modules/editor/global/editorDataLoader.js +91 -0
  226. package/app/modules/editor/global/helpers.js +99 -0
  227. package/app/modules/editor/global/less/colorLabels.less +55 -0
  228. package/app/modules/editor/global/less/editor.less +204 -0
  229. package/app/modules/editor/global/templates/editor.hbs +1 -0
  230. package/app/modules/editor/global/templates/editorPasteZone.hbs +4 -0
  231. package/app/modules/editor/global/templates/partials/part_editorCommon.hbs +70 -0
  232. package/app/modules/editor/global/templates/partials/part_editorItemSidebar.hbs +40 -0
  233. package/app/modules/editor/global/templates/partials/part_editorMenu.hbs +4 -0
  234. package/app/modules/editor/global/templates/partials/part_settingsGeneral.hbs +27 -0
  235. package/app/modules/editor/global/views/editorOriginView.js +255 -0
  236. package/app/modules/editor/global/views/editorPasteZoneView.js +69 -0
  237. package/app/modules/editor/global/views/editorView.js +288 -0
  238. package/app/modules/editor/index.js +52 -0
  239. package/app/modules/editor/menuSettings/index.js +27 -0
  240. package/app/modules/editor/menuSettings/less/menusettings.less +55 -0
  241. package/app/modules/editor/menuSettings/templates/editorMenuSettingsEdit.hbs +1 -0
  242. package/app/modules/editor/menuSettings/templates/editorMenuSettingsEditSidebar.hbs +5 -0
  243. package/app/modules/editor/menuSettings/templates/editorMenuSettingsItem.hbs +4 -0
  244. package/app/modules/editor/menuSettings/views/editorMenuSettingsEditSidebarView.js +21 -0
  245. package/app/modules/editor/menuSettings/views/editorMenuSettingsEditView.js +71 -0
  246. package/app/modules/editor/menuSettings/views/editorMenuSettingsView.js +44 -0
  247. package/app/modules/editor/themeEditor/collections/editorPresetCollection.js +13 -0
  248. package/app/modules/editor/themeEditor/index.js +15 -0
  249. package/app/modules/editor/themeEditor/less/editorPresetEdit.less +78 -0
  250. package/app/modules/editor/themeEditor/less/editorTheming.less +73 -0
  251. package/app/modules/editor/themeEditor/models/editorModel.js +62 -0
  252. package/app/modules/editor/themeEditor/models/editorPresetModel.js +6 -0
  253. package/app/modules/editor/themeEditor/templates/editorPresetEdit.hbs +31 -0
  254. package/app/modules/editor/themeEditor/templates/editorTheming.hbs +35 -0
  255. package/app/modules/editor/themeEditor/templates/editorThemingSidebar.hbs +23 -0
  256. package/app/modules/editor/themeEditor/views/editorPresetEditView.js +98 -0
  257. package/app/modules/editor/themeEditor/views/editorThemingSidebarView.js +62 -0
  258. package/app/modules/editor/themeEditor/views/editorThemingView.js +304 -0
  259. package/app/modules/frameworkImport/index.js +32 -0
  260. package/app/modules/frameworkImport/less/frameworkImport.less +101 -0
  261. package/app/modules/frameworkImport/templates/frameworkImport.hbs +58 -0
  262. package/app/modules/frameworkImport/templates/frameworkImportSidebar.hbs +12 -0
  263. package/app/modules/frameworkImport/templates/frameworkImportSummary.hbs +40 -0
  264. package/app/modules/frameworkImport/templates/partials/part_frameworkImportButton.hbs +3 -0
  265. package/app/modules/frameworkImport/templates/partials/part_frameworkImportStatusMessages.hbs +22 -0
  266. package/app/modules/frameworkImport/views/frameworkImportSidebarView.js +31 -0
  267. package/app/modules/frameworkImport/views/frameworkImportView.js +109 -0
  268. package/app/modules/globalMenu/index.js +86 -0
  269. package/app/modules/globalMenu/less/globalMenu.less +117 -0
  270. package/app/modules/globalMenu/templates/globalMenu.hbs +6 -0
  271. package/app/modules/globalMenu/templates/globalMenuItem.hbs +10 -0
  272. package/app/modules/globalMenu/templates/partials/part_globalMenuButton.hbs +7 -0
  273. package/app/modules/globalMenu/views/globalMenuItemView.js +108 -0
  274. package/app/modules/globalMenu/views/globalMenuView.js +45 -0
  275. package/app/modules/limiteduser/index.js +17 -0
  276. package/app/modules/limiteduser/less/limitedUser.less +29 -0
  277. package/app/modules/limiteduser/templates/limitedUser.hbs +8 -0
  278. package/app/modules/limiteduser/views/limitedUserView.js +12 -0
  279. package/app/modules/modal/index.js +13 -0
  280. package/app/modules/modal/less/modal.less +61 -0
  281. package/app/modules/modal/models/modalModel.js +14 -0
  282. package/app/modules/modal/templates/modal.hbs +34 -0
  283. package/app/modules/modal/views/modalView.js +102 -0
  284. package/app/modules/notify/index.js +32 -0
  285. package/app/modules/notify/plugins/alert/assets/sweetalert.css +932 -0
  286. package/app/modules/notify/plugins/alert/index.js +117 -0
  287. package/app/modules/notify/plugins/alert/less/alert.less +74 -0
  288. package/app/modules/notify/plugins/alert/sweetalert2-11.1.7.all.min.js +2 -0
  289. package/app/modules/notify/plugins/console/index.js +18 -0
  290. package/app/modules/notify/plugins/snackbar/index.js +69 -0
  291. package/app/modules/notify/plugins/snackbar/less/snackbar.less +40 -0
  292. package/app/modules/notify/plugins/toast/index.js +70 -0
  293. package/app/modules/notify/plugins/toast/less/toast.less +62 -0
  294. package/app/modules/pluginManagement/index.js +36 -0
  295. package/app/modules/pluginManagement/less/pluginManagement.less +23 -0
  296. package/app/modules/pluginManagement/less/pluginType.less +25 -0
  297. package/app/modules/pluginManagement/templates/pluginManagement.hbs +43 -0
  298. package/app/modules/pluginManagement/templates/pluginManagementSidebar.hbs +40 -0
  299. package/app/modules/pluginManagement/templates/pluginManagementUpload.hbs +12 -0
  300. package/app/modules/pluginManagement/templates/pluginManagementUploadSidebar.hbs +13 -0
  301. package/app/modules/pluginManagement/templates/pluginType.hbs +43 -0
  302. package/app/modules/pluginManagement/views/pluginManagementSidebarView.js +48 -0
  303. package/app/modules/pluginManagement/views/pluginManagementUploadSidebarView.js +25 -0
  304. package/app/modules/pluginManagement/views/pluginManagementUploadView.js +52 -0
  305. package/app/modules/pluginManagement/views/pluginManagementView.js +104 -0
  306. package/app/modules/pluginManagement/views/pluginTypeView.js +105 -0
  307. package/app/modules/projects/assets/origami-project.jpg +0 -0
  308. package/app/modules/projects/index.js +82 -0
  309. package/app/modules/projects/less/projects.less +152 -0
  310. package/app/modules/projects/less/qproject.less +143 -0
  311. package/app/modules/projects/templates/project.hbs +63 -0
  312. package/app/modules/projects/templates/projects.hbs +6 -0
  313. package/app/modules/projects/templates/projectsSidebar.hbs +42 -0
  314. package/app/modules/projects/views/projectView.js +167 -0
  315. package/app/modules/projects/views/projectsSidebarView.js +172 -0
  316. package/app/modules/projects/views/projectsView.js +271 -0
  317. package/app/modules/scaffold/backboneFormsOverrides.js +284 -0
  318. package/app/modules/scaffold/index.js +342 -0
  319. package/app/modules/scaffold/lang/en.json +9 -0
  320. package/app/modules/scaffold/less/codeEditor.less +6 -0
  321. package/app/modules/scaffold/less/colourPicker.less +141 -0
  322. package/app/modules/scaffold/less/displayTitle.less +17 -0
  323. package/app/modules/scaffold/less/forms.less +174 -0
  324. package/app/modules/scaffold/less/list.less +35 -0
  325. package/app/modules/scaffold/less/modal.less +45 -0
  326. package/app/modules/scaffold/less/scaffoldAsset.less +23 -0
  327. package/app/modules/scaffold/less/scaffoldAssetItem.less +50 -0
  328. package/app/modules/scaffold/less/selectize.less +29 -0
  329. package/app/modules/scaffold/less/textArea.less +9 -0
  330. package/app/modules/scaffold/less/users.less +6 -0
  331. package/app/modules/scaffold/templates/field.hbs +25 -0
  332. package/app/modules/scaffold/templates/fieldset.hbs +10 -0
  333. package/app/modules/scaffold/templates/form.hbs +6 -0
  334. package/app/modules/scaffold/templates/list.hbs +4 -0
  335. package/app/modules/scaffold/templates/listItem.hbs +5 -0
  336. package/app/modules/scaffold/templates/scaffoldAsset.hbs +65 -0
  337. package/app/modules/scaffold/templates/scaffoldAssetItem.hbs +35 -0
  338. package/app/modules/scaffold/templates/scaffoldDisplayTitle.hbs +8 -0
  339. package/app/modules/scaffold/templates/scaffoldEditHtml.hbs +1 -0
  340. package/app/modules/scaffold/templates/scaffoldFile.hbs +1 -0
  341. package/app/modules/scaffold/templates/scaffoldItemsModal.hbs +14 -0
  342. package/app/modules/scaffold/templates/scaffoldModalOverlay.hbs +1 -0
  343. package/app/modules/scaffold/templates/scaffoldUsersOption.hbs +8 -0
  344. package/app/modules/scaffold/views/scaffoldAssetItemView.js +203 -0
  345. package/app/modules/scaffold/views/scaffoldAssetView.js +157 -0
  346. package/app/modules/scaffold/views/scaffoldCodeEditorView.js +64 -0
  347. package/app/modules/scaffold/views/scaffoldColourPickerView.js +70 -0
  348. package/app/modules/scaffold/views/scaffoldDisplayTitleView.js +112 -0
  349. package/app/modules/scaffold/views/scaffoldFileView.js +18 -0
  350. package/app/modules/scaffold/views/scaffoldItemsModalView.js +100 -0
  351. package/app/modules/scaffold/views/scaffoldListView.js +158 -0
  352. package/app/modules/scaffold/views/scaffoldTagsView.js +71 -0
  353. package/app/modules/scaffold/views/scaffoldUsersView.js +89 -0
  354. package/app/modules/sidebar/index.js +22 -0
  355. package/app/modules/sidebar/less/sidebar.less +298 -0
  356. package/app/modules/sidebar/less/sidebarFilter.less +91 -0
  357. package/app/modules/sidebar/templates/sidebar.hbs +7 -0
  358. package/app/modules/sidebar/templates/sidebarBreadcrumb.hbs +3 -0
  359. package/app/modules/sidebar/templates/sidebarDivide.hbs +5 -0
  360. package/app/modules/sidebar/templates/sidebarFieldsetFilter.hbs +6 -0
  361. package/app/modules/sidebar/templates/sidebarFilter.hbs +30 -0
  362. package/app/modules/sidebar/templates/sidebarRowFilter.hbs +4 -0
  363. package/app/modules/sidebar/templates/sidebarUpdateButton.hbs +3 -0
  364. package/app/modules/sidebar/views/sidebarFieldsetFilterView.js +43 -0
  365. package/app/modules/sidebar/views/sidebarFilterView.js +132 -0
  366. package/app/modules/sidebar/views/sidebarItemView.js +172 -0
  367. package/app/modules/sidebar/views/sidebarView.js +71 -0
  368. package/app/modules/user/assets/adapt-learning-logo.png +0 -0
  369. package/app/modules/user/assets/adapt-logo.png +0 -0
  370. package/app/modules/user/assets/login_bg.jpg +0 -0
  371. package/app/modules/user/index.js +75 -0
  372. package/app/modules/user/less/forgotPassword.less +14 -0
  373. package/app/modules/user/less/login.less +138 -0
  374. package/app/modules/user/less/logout.less +3 -0
  375. package/app/modules/user/less/resetPassword.less +7 -0
  376. package/app/modules/user/less/userProfile.less +49 -0
  377. package/app/modules/user/models/userProfileModel.js +32 -0
  378. package/app/modules/user/templates/forgotPassword.hbs +36 -0
  379. package/app/modules/user/templates/login.hbs +29 -0
  380. package/app/modules/user/templates/resetPassword.hbs +29 -0
  381. package/app/modules/user/templates/userProfile.hbs +71 -0
  382. package/app/modules/user/templates/userProfileSidebar.hbs +16 -0
  383. package/app/modules/user/views/forgotPasswordView.js +63 -0
  384. package/app/modules/user/views/loginView.js +93 -0
  385. package/app/modules/user/views/resetPasswordView.js +78 -0
  386. package/app/modules/user/views/userProfileSidebarView.js +32 -0
  387. package/app/modules/user/views/userProfileView.js +107 -0
  388. package/app/modules/userManagement/collections/userCollection.js +65 -0
  389. package/app/modules/userManagement/helpers.js +46 -0
  390. package/app/modules/userManagement/index.js +67 -0
  391. package/app/modules/userManagement/less/userManagement.less +268 -0
  392. package/app/modules/userManagement/models/userModel.js +34 -0
  393. package/app/modules/userManagement/templates/addUser.hbs +35 -0
  394. package/app/modules/userManagement/templates/addUserSidebar.hbs +9 -0
  395. package/app/modules/userManagement/templates/user.hbs +126 -0
  396. package/app/modules/userManagement/templates/userManagement.hbs +49 -0
  397. package/app/modules/userManagement/templates/userManagementFilter.hbs +19 -0
  398. package/app/modules/userManagement/templates/userManagementSidebar.hbs +13 -0
  399. package/app/modules/userManagement/views/addUserSidebarView.js +25 -0
  400. package/app/modules/userManagement/views/addUserView.js +85 -0
  401. package/app/modules/userManagement/views/filterView.js +49 -0
  402. package/app/modules/userManagement/views/userManagementSidebarView.js +33 -0
  403. package/app/modules/userManagement/views/userManagementView.js +104 -0
  404. package/app/modules/userManagement/views/userView.js +324 -0
  405. package/conf/config.schema.json +18 -0
  406. package/docs/migrating-from-legacy.md +19 -0
  407. package/docs/plugins/index-ui.md +17 -0
  408. package/docs/plugins/uidocs.js +54 -0
  409. package/docs/ui-extensions.md +15 -0
  410. package/index.js +1 -0
  411. package/lib/UiBuild.js +391 -0
  412. package/lib/UiModule.js +158 -0
  413. package/npm_hooks/postinstall.js +4 -0
  414. package/package.json +27 -0
@@ -0,0 +1,4500 @@
1
+ /**
2
+ * Selectize (v0.15.2)
3
+ * https://selectize.dev
4
+ *
5
+ * Copyright (c) 2013-2015 Brian Reavis & contributors
6
+ * Copyright (c) 2020-2022 Selectize Team & contributors
7
+ *
8
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
9
+ * file except in compliance with the License. You may obtain a copy of the License at:
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software distributed under
13
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
14
+ * ANY KIND, either express or implied. See the License for the specific language
15
+ * governing permissions and limitations under the License.
16
+ *
17
+ * @author Brian Reavis <brian@thirdroute.com>
18
+ * @author Ris Adams <selectize@risadams.com>
19
+ */
20
+ (function (root, factory) {
21
+ if (typeof define === 'function' && define.amd) {
22
+ define(['jquery'], factory);
23
+ } else if (typeof module === 'object' && typeof module.exports === 'object') {
24
+ module.exports = factory(require('jquery'));
25
+ } else {
26
+ root.Selectize = factory(root.jQuery);
27
+ }
28
+ }(this, function ($) {
29
+ 'use strict';
30
+ /**
31
+ * highlight v3 | MIT license | Johann Burkard <jb@eaio.com>
32
+ * Highlights arbitrary terms in a node.
33
+ *
34
+ * - Modified by Marshal <beatgates@gmail.com> 2011-6-24 (added regex)
35
+ * - Modified by Brian Reavis <brian@thirdroute.com> 2012-8-27 (cleanup)
36
+ */
37
+
38
+ var highlight = function ($element, pattern) {
39
+ if (typeof pattern === 'string' && !pattern.length) return;
40
+ var regex = (typeof pattern === 'string') ? new RegExp(pattern, 'i') : pattern;
41
+
42
+ var highlight = function (node) {
43
+ var skip = 0;
44
+ // Wrap matching part of text node with highlighting <span>, e.g.
45
+ // Soccer -> <span class="highlight">Soc</span>cer for regex = /soc/i
46
+ if (node.nodeType === 3) {
47
+ var pos = node.data.search(regex);
48
+ if (pos >= 0 && node.data.length > 0) {
49
+ var match = node.data.match(regex);
50
+ var spannode = document.createElement('span');
51
+ spannode.className = 'highlight';
52
+ var middlebit = node.splitText(pos);
53
+ var endbit = middlebit.splitText(match[0].length);
54
+ var middleclone = middlebit.cloneNode(true);
55
+ spannode.appendChild(middleclone);
56
+ middlebit.parentNode.replaceChild(spannode, middlebit);
57
+ skip = 1;
58
+ }
59
+ }
60
+ // Recurse element node, looking for child text nodes to highlight, unless element
61
+ // is childless, <script>, <style>, or already highlighted: <span class="highlight">
62
+ else if (node.nodeType === 1 && node.childNodes && !/(script|style)/i.test(node.tagName) && (node.className !== 'highlight' || node.tagName !== 'SPAN')) {
63
+ for (var i = 0; i < node.childNodes.length; ++i) {
64
+ i += highlight(node.childNodes[i]);
65
+ }
66
+ }
67
+ return skip;
68
+ };
69
+
70
+ return $element.each(function () {
71
+ highlight(this);
72
+ });
73
+ };
74
+
75
+ /**
76
+ * removeHighlight fn copied from highlight v5 and
77
+ * edited to remove with() and pass js strict mode
78
+ */
79
+ $.fn.removeHighlight = function () {
80
+ return this.find("span.highlight").each(function () {
81
+ this.parentNode.firstChild.nodeName;
82
+ var parent = this.parentNode;
83
+ parent.replaceChild(this.firstChild, this);
84
+ parent.normalize();
85
+ }).end();
86
+ };
87
+
88
+ /**
89
+ * MicroEvent - to make any js object an event emitter
90
+ *
91
+ * - pure javascript - server compatible, browser compatible
92
+ * - don't rely on the browser doms
93
+ * - super simple - you get it immediately, no mystery, no magic involved
94
+ *
95
+ * @author Jerome Etienne (https://github.com/jeromeetienne)
96
+ */
97
+
98
+ var MicroEvent = function () { };
99
+ MicroEvent.prototype = {
100
+ on: function (event, fct) {
101
+ this._events = this._events || {};
102
+ this._events[event] = this._events[event] || [];
103
+ this._events[event].push(fct);
104
+ },
105
+ off: function (event, fct) {
106
+ var n = arguments.length;
107
+ if (n === 0) return delete this._events;
108
+ if (n === 1) return delete this._events[event];
109
+
110
+ this._events = this._events || {};
111
+ if (event in this._events === false) return;
112
+ this._events[event].splice(this._events[event].indexOf(fct), 1);
113
+ },
114
+ trigger: function (event /* , args... */) {
115
+ const events = this._events = this._events || {};
116
+ if (event in events === false) return;
117
+ for (var i = 0; i < events[event].length; i++) {
118
+ events[event][i].apply(this, Array.prototype.slice.call(arguments, 1));
119
+ }
120
+ }
121
+ };
122
+
123
+ /**
124
+ * Mixin will delegate all MicroEvent.js function in the destination object.
125
+ *
126
+ * - MicroEvent.mixin(Foobar) will make Foobar able to use MicroEvent
127
+ *
128
+ * @param {object} the object which will support MicroEvent
129
+ */
130
+ MicroEvent.mixin = function (destObject) {
131
+ var props = ['on', 'off', 'trigger'];
132
+ for (var i = 0; i < props.length; i++) {
133
+ destObject.prototype[props[i]] = MicroEvent.prototype[props[i]];
134
+ }
135
+ };
136
+
137
+ /**
138
+ * microplugin.js
139
+ * Copyright (c) 2013 Brian Reavis & contributors
140
+ * Copyright (c) 2022 Selectize Team & contributors
141
+ *
142
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
143
+ * file except in compliance with the License. You may obtain a copy of the License at:
144
+ * http://www.apache.org/licenses/LICENSE-2.0
145
+ *
146
+ * Unless required by applicable law or agreed to in writing, software distributed under
147
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
148
+ * ANY KIND, either express or implied. See the License for the specific language
149
+ * governing permissions and limitations under the License.
150
+ *
151
+ * @author Brian Reavis <brian@thirdroute.com>
152
+ * @author Ris Adams <selectize@risadams.com>
153
+ */
154
+
155
+ var MicroPlugin = {};
156
+ MicroPlugin.mixin = function (Interface) {
157
+ Interface.plugins = {};
158
+
159
+ /**
160
+ * Initializes the listed plugins (with options).
161
+ * Acceptable formats:
162
+ *
163
+ * List (without options):
164
+ * ['a', 'b', 'c']
165
+ *
166
+ * List (with options):
167
+ * [{'name': 'a', options: {}}, {'name': 'b', options: {}}]
168
+ *
169
+ * Hash (with options):
170
+ * {'a': { ... }, 'b': { ... }, 'c': { ... }}
171
+ *
172
+ * @param {mixed} plugins
173
+ */
174
+ Interface.prototype.initializePlugins = function (plugins) {
175
+ var i, n, key;
176
+ var self = this;
177
+ var queue = [];
178
+
179
+ self.plugins = {
180
+ names: [],
181
+ settings: {},
182
+ requested: {},
183
+ loaded: {}
184
+ };
185
+
186
+ if (utils.isArray(plugins)) {
187
+ for (i = 0, n = plugins.length; i < n; i++) {
188
+ if (typeof plugins[i] === 'string') {
189
+ queue.push(plugins[i]);
190
+ } else {
191
+ self.plugins.settings[plugins[i].name] = plugins[i].options;
192
+ queue.push(plugins[i].name);
193
+ }
194
+ }
195
+ } else if (plugins) {
196
+ for (key in plugins) {
197
+ if (plugins.hasOwnProperty(key)) {
198
+ self.plugins.settings[key] = plugins[key];
199
+ queue.push(key);
200
+ }
201
+ }
202
+ }
203
+
204
+ while (queue.length) {
205
+ self.require(queue.shift());
206
+ }
207
+ };
208
+
209
+ Interface.prototype.loadPlugin = function (name) {
210
+ var self = this;
211
+ var plugins = self.plugins;
212
+ var plugin = Interface.plugins[name];
213
+
214
+ if (!Interface.plugins.hasOwnProperty(name)) {
215
+ throw new Error('Unable to find "' + name + '" plugin');
216
+ }
217
+
218
+ plugins.requested[name] = true;
219
+ plugins.loaded[name] = plugin.fn.apply(self, [self.plugins.settings[name] || {}]);
220
+ plugins.names.push(name);
221
+ };
222
+
223
+ /**
224
+ * Initializes a plugin.
225
+ *
226
+ * @param {string} name
227
+ */
228
+ Interface.prototype.require = function (name) {
229
+ var self = this;
230
+ var plugins = self.plugins;
231
+
232
+ if (!self.plugins.loaded.hasOwnProperty(name)) {
233
+ if (plugins.requested[name]) {
234
+ throw new Error('Plugin has circular dependency ("' + name + '")');
235
+ }
236
+ self.loadPlugin(name);
237
+ }
238
+
239
+ return plugins.loaded[name];
240
+ };
241
+
242
+ /**
243
+ * Registers a plugin.
244
+ *
245
+ * @param {string} name
246
+ * @param {function} fn
247
+ */
248
+ Interface.define = function (name, fn) {
249
+ Interface.plugins[name] = {
250
+ 'name': name,
251
+ 'fn': fn
252
+ };
253
+ };
254
+ };
255
+
256
+ var utils = {
257
+ isArray: Array.isArray || function (vArg) {
258
+ return Object.prototype.toString.call(vArg) === '[object Array]';
259
+ }
260
+ };
261
+
262
+
263
+ /**
264
+ * sifter.js
265
+ * Copyright (c) 2013–2020 Brian Reavis & contributors
266
+ * Copyright (c) 2022 Selectize Team & contributors
267
+ *
268
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
269
+ * file except in compliance with the License. You may obtain a copy of the License at:
270
+ * http://www.apache.org/licenses/LICENSE-2.0
271
+ *
272
+ * Unless required by applicable law or agreed to in writing, software distributed under
273
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
274
+ * ANY KIND, either express or implied. See the License for the specific language
275
+ * governing permissions and limitations under the License.
276
+ *
277
+ * @author Brian Reavis <brian@thirdroute.com>
278
+ * @author Ris Adams <selectize@risadams.com>
279
+ */
280
+
281
+ /**
282
+ * Textually searches arrays and hashes of objects
283
+ * by property (or multiple properties). Designed
284
+ * specifically for autocomplete.
285
+ *
286
+ * @constructor
287
+ * @param {array|object} items
288
+ * @param {object} items
289
+ */
290
+ var Sifter = function (items, settings) {
291
+ this.items = items;
292
+ this.settings = settings || { diacritics: true };
293
+ };
294
+
295
+ /**
296
+ * Splits a search string into an array of individual
297
+ * regexps to be used to match results.
298
+ *
299
+ * @param {string} query
300
+ * @returns {array}
301
+ */
302
+ Sifter.prototype.tokenize = function (query, respect_word_boundaries) {
303
+ query = trim(String(query || '').toLowerCase());
304
+ if (!query || !query.length) return [];
305
+
306
+ var i, n, regex, letter;
307
+ var tokens = [];
308
+ var words = query.split(/ +/);
309
+
310
+ for (i = 0, n = words.length; i < n; i++) {
311
+ regex = escape_regex(words[i]);
312
+ if (this.settings.diacritics) {
313
+ for (letter in DIACRITICS) {
314
+ if (DIACRITICS.hasOwnProperty(letter)) {
315
+ regex = regex.replace(new RegExp(letter, 'g'), DIACRITICS[letter]);
316
+ }
317
+ }
318
+ }
319
+ if (respect_word_boundaries) regex = "\\b" + regex
320
+ tokens.push({
321
+ string: words[i],
322
+ regex: new RegExp(regex, 'i')
323
+ });
324
+ }
325
+
326
+ return tokens;
327
+ };
328
+
329
+ /**
330
+ * Iterates over arrays and hashes.
331
+ *
332
+ * ```
333
+ * this.iterator(this.items, function(item, id) {
334
+ * // invoked for each item
335
+ * });
336
+ * ```
337
+ *
338
+ * @param {array|object} object
339
+ */
340
+ Sifter.prototype.iterator = function (object, callback) {
341
+ var iterator;
342
+ if (is_array(object)) {
343
+ iterator = Array.prototype.forEach || function (callback) {
344
+ for (var i = 0, n = this.length; i < n; i++) {
345
+ callback(this[i], i, this);
346
+ }
347
+ };
348
+ } else {
349
+ iterator = function (callback) {
350
+ for (var key in this) {
351
+ if (this.hasOwnProperty(key)) {
352
+ callback(this[key], key, this);
353
+ }
354
+ }
355
+ };
356
+ }
357
+
358
+ iterator.apply(object, [callback]);
359
+ };
360
+
361
+ /**
362
+ * Returns a function to be used to score individual results.
363
+ *
364
+ * Good matches will have a higher score than poor matches.
365
+ * If an item is not a match, 0 will be returned by the function.
366
+ *
367
+ * @param {object|string} search
368
+ * @param {object} options (optional)
369
+ * @returns {function}
370
+ */
371
+ Sifter.prototype.getScoreFunction = function (search, options) {
372
+ var self, fields, tokens, token_count, nesting;
373
+
374
+ self = this;
375
+ search = self.prepareSearch(search, options);
376
+ tokens = search.tokens;
377
+ fields = search.options.fields;
378
+ token_count = tokens.length;
379
+ nesting = search.options.nesting;
380
+
381
+ /**
382
+ * Calculates how close of a match the
383
+ * given value is against a search token.
384
+ *
385
+ * @param {mixed} value
386
+ * @param {object} token
387
+ * @return {number}
388
+ */
389
+ var scoreValue = function (value, token) {
390
+ var score, pos;
391
+
392
+ if (!value) return 0;
393
+ value = String(value || '');
394
+ pos = value.search(token.regex);
395
+ if (pos === -1) return 0;
396
+ score = token.string.length / value.length;
397
+ if (pos === 0) score += 0.5;
398
+ return score;
399
+ };
400
+
401
+ /**
402
+ * Calculates the score of an object
403
+ * against the search query.
404
+ *
405
+ * @param {object} token
406
+ * @param {object} data
407
+ * @return {number}
408
+ */
409
+ var scoreObject = (function () {
410
+ var field_count = fields.length;
411
+ if (!field_count) {
412
+ return function () { return 0; };
413
+ }
414
+ if (field_count === 1) {
415
+ return function (token, data) {
416
+ return scoreValue(getattr(data, fields[0], nesting), token);
417
+ };
418
+ }
419
+ return function (token, data) {
420
+ for (var i = 0, sum = 0; i < field_count; i++) {
421
+ sum += scoreValue(getattr(data, fields[i], nesting), token);
422
+ }
423
+ return sum / field_count;
424
+ };
425
+ })();
426
+
427
+ if (!token_count) {
428
+ return function () { return 0; };
429
+ }
430
+ if (token_count === 1) {
431
+ return function (data) {
432
+ return scoreObject(tokens[0], data);
433
+ };
434
+ }
435
+
436
+ if (search.options.conjunction === 'and') {
437
+ return function (data) {
438
+ var score;
439
+ for (var i = 0, sum = 0; i < token_count; i++) {
440
+ score = scoreObject(tokens[i], data);
441
+ if (score <= 0) return 0;
442
+ sum += score;
443
+ }
444
+ return sum / token_count;
445
+ };
446
+ } else {
447
+ return function (data) {
448
+ for (var i = 0, sum = 0; i < token_count; i++) {
449
+ sum += scoreObject(tokens[i], data);
450
+ }
451
+ return sum / token_count;
452
+ };
453
+ }
454
+ };
455
+
456
+ /**
457
+ * Returns a function that can be used to compare two
458
+ * results, for sorting purposes. If no sorting should
459
+ * be performed, `null` will be returned.
460
+ *
461
+ * @param {string|object} search
462
+ * @param {object} options
463
+ * @return function(a,b)
464
+ */
465
+ Sifter.prototype.getSortFunction = function (search, options) {
466
+ var i, n, self, field, fields, fields_count, multiplier, multipliers, get_field, implicit_score, sort;
467
+
468
+ self = this;
469
+ search = self.prepareSearch(search, options);
470
+ sort = (!search.query && options.sort_empty) || options.sort;
471
+
472
+ /**
473
+ * Fetches the specified sort field value
474
+ * from a search result item.
475
+ *
476
+ * @param {string} name
477
+ * @param {object} result
478
+ * @return {mixed}
479
+ */
480
+ get_field = function (name, result) {
481
+ if (name === '$score') return result.score;
482
+ return getattr(self.items[result.id], name, options.nesting);
483
+ };
484
+
485
+ // parse options
486
+ fields = [];
487
+ if (sort) {
488
+ for (i = 0, n = sort.length; i < n; i++) {
489
+ if (search.query || sort[i].field !== '$score') {
490
+ fields.push(sort[i]);
491
+ }
492
+ }
493
+ }
494
+
495
+ // the "$score" field is implied to be the primary
496
+ // sort field, unless it's manually specified
497
+ if (search.query) {
498
+ implicit_score = true;
499
+ for (i = 0, n = fields.length; i < n; i++) {
500
+ if (fields[i].field === '$score') {
501
+ implicit_score = false;
502
+ break;
503
+ }
504
+ }
505
+ if (implicit_score) {
506
+ fields.unshift({ field: '$score', direction: 'desc' });
507
+ }
508
+ } else {
509
+ for (i = 0, n = fields.length; i < n; i++) {
510
+ if (fields[i].field === '$score') {
511
+ fields.splice(i, 1);
512
+ break;
513
+ }
514
+ }
515
+ }
516
+
517
+ multipliers = [];
518
+ for (i = 0, n = fields.length; i < n; i++) {
519
+ multipliers.push(fields[i].direction === 'desc' ? -1 : 1);
520
+ }
521
+
522
+ // build function
523
+ fields_count = fields.length;
524
+ if (!fields_count) {
525
+ return null;
526
+ } else if (fields_count === 1) {
527
+ field = fields[0].field;
528
+ multiplier = multipliers[0];
529
+ return function (a, b) {
530
+ return multiplier * cmp(
531
+ get_field(field, a),
532
+ get_field(field, b)
533
+ );
534
+ };
535
+ } else {
536
+ return function (a, b) {
537
+ var i, result, a_value, b_value, field;
538
+ for (i = 0; i < fields_count; i++) {
539
+ field = fields[i].field;
540
+ result = multipliers[i] * cmp(
541
+ get_field(field, a),
542
+ get_field(field, b)
543
+ );
544
+ if (result) return result;
545
+ }
546
+ return 0;
547
+ };
548
+ }
549
+ };
550
+
551
+ /**
552
+ * Parses a search query and returns an object
553
+ * with tokens and fields ready to be populated
554
+ * with results.
555
+ *
556
+ * @param {string} query
557
+ * @param {object} options
558
+ * @returns {object}
559
+ */
560
+ Sifter.prototype.prepareSearch = function (query, options) {
561
+ if (typeof query === 'object') return query;
562
+
563
+ options = extend({}, options);
564
+
565
+ var option_fields = options.fields;
566
+ var option_sort = options.sort;
567
+ var option_sort_empty = options.sort_empty;
568
+
569
+ if (option_fields && !is_array(option_fields)) options.fields = [option_fields];
570
+ if (option_sort && !is_array(option_sort)) options.sort = [option_sort];
571
+ if (option_sort_empty && !is_array(option_sort_empty)) options.sort_empty = [option_sort_empty];
572
+
573
+ return {
574
+ options: options,
575
+ query: String(query || '').toLowerCase(),
576
+ tokens: this.tokenize(query, options.respect_word_boundaries),
577
+ total: 0,
578
+ items: []
579
+ };
580
+ };
581
+
582
+ /**
583
+ * Searches through all items and returns a sorted array of matches.
584
+ *
585
+ * The `options` parameter can contain:
586
+ *
587
+ * - fields {string|array}
588
+ * - sort {array}
589
+ * - score {function}
590
+ * - filter {bool}
591
+ * - limit {integer}
592
+ *
593
+ * Returns an object containing:
594
+ *
595
+ * - options {object}
596
+ * - query {string}
597
+ * - tokens {array}
598
+ * - total {int}
599
+ * - items {array}
600
+ *
601
+ * @param {string} query
602
+ * @param {object} options
603
+ * @returns {object}
604
+ */
605
+ Sifter.prototype.search = function (query, options) {
606
+ var self = this, value, score, search, calculateScore;
607
+ var fn_sort;
608
+ var fn_score;
609
+
610
+ search = this.prepareSearch(query, options);
611
+ options = search.options;
612
+ query = search.query;
613
+
614
+ // generate result scoring function
615
+ fn_score = options.score || self.getScoreFunction(search);
616
+
617
+ // perform search and sort
618
+ if (query.length) {
619
+ self.iterator(self.items, function (item, id) {
620
+ score = fn_score(item);
621
+ if (options.filter === false || score > 0) {
622
+ search.items.push({ 'score': score, 'id': id });
623
+ }
624
+ });
625
+ } else {
626
+ self.iterator(self.items, function (item, id) {
627
+ search.items.push({ 'score': 1, 'id': id });
628
+ });
629
+ }
630
+
631
+ fn_sort = self.getSortFunction(search, options);
632
+ if (fn_sort) search.items.sort(fn_sort);
633
+
634
+ // apply limits
635
+ search.total = search.items.length;
636
+ if (typeof options.limit === 'number') {
637
+ search.items = search.items.slice(0, options.limit);
638
+ }
639
+
640
+ return search;
641
+ };
642
+
643
+ // utilities
644
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
645
+
646
+ var cmp = function (a, b) {
647
+ if (typeof a === 'number' && typeof b === 'number') {
648
+ return a > b ? 1 : (a < b ? -1 : 0);
649
+ }
650
+ a = asciifold(String(a || ''));
651
+ b = asciifold(String(b || ''));
652
+ if (a > b) return 1;
653
+ if (b > a) return -1;
654
+ return 0;
655
+ };
656
+
657
+ var extend = function (a, b) {
658
+ var i, n, k, object;
659
+ for (i = 1, n = arguments.length; i < n; i++) {
660
+ object = arguments[i];
661
+ if (!object) continue;
662
+ for (k in object) {
663
+ if (object.hasOwnProperty(k)) {
664
+ a[k] = object[k];
665
+ }
666
+ }
667
+ }
668
+ return a;
669
+ };
670
+
671
+ /**
672
+ * A property getter resolving dot-notation
673
+ * @param {Object} obj The root object to fetch property on
674
+ * @param {String} name The optionally dotted property name to fetch
675
+ * @param {Boolean} nesting Handle nesting or not
676
+ * @return {Object} The resolved property value
677
+ */
678
+ var getattr = function (obj, name, nesting) {
679
+ if (!obj || !name) return;
680
+ if (!nesting) return obj[name];
681
+ var names = name.split(".");
682
+ while (names.length && (obj = obj[names.shift()]));
683
+ return obj;
684
+ };
685
+
686
+ var trim = function (str) {
687
+ return (str + '').replace(/^\s+|\s+$|/g, '');
688
+ };
689
+
690
+ var escape_regex = function (str) {
691
+ return (str + '').replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
692
+ };
693
+
694
+ var is_array = Array.isArray || (typeof $ !== 'undefined' && $.isArray) || function (object) {
695
+ return Object.prototype.toString.call(object) === '[object Array]';
696
+ };
697
+
698
+ var DIACRITICS = {
699
+ 'a': '[aḀḁĂăÂâǍǎȺⱥȦȧẠạÄäÀàÁáĀāÃãÅåąĄÃąĄ]',
700
+ 'b': '[b␢βΒB฿𐌁ᛒ]',
701
+ 'c': '[cĆćĈĉČčĊċC̄c̄ÇçḈḉȻȼƇƈɕᴄCc]',
702
+ 'd': '[dĎďḊḋḐḑḌḍḒḓḎḏĐđD̦d̦ƉɖƊɗƋƌᵭᶁᶑȡᴅDdð]',
703
+ 'e': '[eÉéÈèÊêḘḙĚěĔĕẼẽḚḛẺẻĖėËëĒēȨȩĘęᶒɆɇȄȅẾếỀềỄễỂểḜḝḖḗḔḕȆȇẸẹỆệⱸᴇEeɘǝƏƐε]',
704
+ 'f': '[fƑƒḞḟ]',
705
+ 'g': '[gɢ₲ǤǥĜĝĞğĢģƓɠĠġ]',
706
+ 'h': '[hĤĥĦħḨḩẖẖḤḥḢḣɦʰǶƕ]',
707
+ 'i': '[iÍíÌìĬĭÎîǏǐÏïḮḯĨĩĮįĪīỈỉȈȉȊȋỊịḬḭƗɨɨ̆ᵻᶖİiIıɪIi]',
708
+ 'j': '[jȷĴĵɈɉʝɟʲ]',
709
+ 'k': '[kƘƙꝀꝁḰḱǨǩḲḳḴḵκϰ₭]',
710
+ 'l': '[lŁłĽľĻļĹĺḶḷḸḹḼḽḺḻĿŀȽƚⱠⱡⱢɫɬᶅɭȴʟLl]',
711
+ 'n': '[nŃńǸǹŇňÑñṄṅŅņṆṇṊṋṈṉN̈n̈ƝɲȠƞᵰᶇɳȵɴNnŊŋ]',
712
+ 'o': '[oØøÖöÓóÒòÔôǑǒŐőŎŏȮȯỌọƟɵƠơỎỏŌōÕõǪǫȌȍՕօ]',
713
+ 'p': '[pṔṕṖṗⱣᵽƤƥᵱ]',
714
+ 'q': '[qꝖꝗʠɊɋꝘꝙq̃]',
715
+ 'r': '[rŔŕɌɍŘřŖŗṘṙȐȑȒȓṚṛⱤɽ]',
716
+ 's': '[sŚśṠṡṢṣꞨꞩŜŝŠšŞşȘșS̈s̈]',
717
+ 't': '[tŤťṪṫŢţṬṭƮʈȚțṰṱṮṯƬƭ]',
718
+ 'u': '[uŬŭɄʉỤụÜüÚúÙùÛûǓǔŰűŬŭƯưỦủŪūŨũŲųȔȕ∪]',
719
+ 'v': '[vṼṽṾṿƲʋꝞꝟⱱʋ]',
720
+ 'w': '[wẂẃẀẁŴŵẄẅẆẇẈẉ]',
721
+ 'x': '[xẌẍẊẋχ]',
722
+ 'y': '[yÝýỲỳŶŷŸÿỸỹẎẏỴỵɎɏƳƴ]',
723
+ 'z': '[zŹźẐẑŽžŻżẒẓẔẕƵƶ]'
724
+ };
725
+
726
+ var asciifold = (function () {
727
+ var i, n, k, chunk;
728
+ var foreignletters = '';
729
+ var lookup = {};
730
+ for (k in DIACRITICS) {
731
+ if (DIACRITICS.hasOwnProperty(k)) {
732
+ chunk = DIACRITICS[k].substring(2, DIACRITICS[k].length - 1);
733
+ foreignletters += chunk;
734
+ for (i = 0, n = chunk.length; i < n; i++) {
735
+ lookup[chunk.charAt(i)] = k;
736
+ }
737
+ }
738
+ }
739
+ var regexp = new RegExp('[' + foreignletters + ']', 'g');
740
+ return function (str) {
741
+ return str.replace(regexp, function (foreignletter) {
742
+ return lookup[foreignletter];
743
+ }).toLowerCase();
744
+ };
745
+ })();
746
+
747
+
748
+ // export
749
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
750
+
751
+ function uaDetect(platform, re) {
752
+ if (navigator.userAgentData) {
753
+ return platform === navigator.userAgentData.platform;
754
+ }
755
+
756
+ return re.test(navigator.userAgent);
757
+ }
758
+
759
+ var IS_MAC = uaDetect("macOS", /Mac/);
760
+
761
+ var KEY_A = 65;
762
+ var KEY_COMMA = 188;
763
+ var KEY_RETURN = 13;
764
+ var KEY_ESC = 27;
765
+ var KEY_LEFT = 37;
766
+ var KEY_UP = 38;
767
+ var KEY_P = 80;
768
+ var KEY_RIGHT = 39;
769
+ var KEY_DOWN = 40;
770
+ var KEY_N = 78;
771
+ var KEY_BACKSPACE = 8;
772
+ var KEY_DELETE = 46;
773
+ var KEY_SHIFT = 16;
774
+ var KEY_CMD = IS_MAC ? 91 : 17;
775
+ var KEY_CTRL = IS_MAC ? 18 : 17;
776
+ var KEY_TAB = 9;
777
+
778
+ var TAG_SELECT = 1;
779
+ var TAG_INPUT = 2;
780
+
781
+ // for now, android support in general is too spotty to support validity
782
+ var SUPPORTS_VALIDITY_API = !uaDetect("Android", /android/i) && !!document.createElement('input').validity;
783
+
784
+ /**
785
+ * Determines if the provided value has been defined.
786
+ *
787
+ * @param {mixed} object
788
+ * @returns {boolean}
789
+ */
790
+ var isset = function (object) {
791
+ return typeof object !== 'undefined';
792
+ };
793
+
794
+ /**
795
+ * Converts a scalar to its best string representation
796
+ * for hash keys and HTML attribute values.
797
+ *
798
+ * Transformations:
799
+ * 'str' -> 'str'
800
+ * null -> ''
801
+ * undefined -> ''
802
+ * true -> '1'
803
+ * false -> '0'
804
+ * 0 -> '0'
805
+ * 1 -> '1'
806
+ *
807
+ * @param {string} value
808
+ * @returns {string|null}
809
+ */
810
+ var hash_key = function (value) {
811
+ if (typeof value === 'undefined' || value === null) return null;
812
+ if (typeof value === 'boolean') return value ? '1' : '0';
813
+ return value + '';
814
+ };
815
+
816
+ /**
817
+ * Escapes a string for use within HTML.
818
+ *
819
+ * @param {string} str
820
+ * @returns {string}
821
+ */
822
+ var escape_html = function (str) {
823
+ return (str + '')
824
+ .replace(/&/g, '&amp;')
825
+ .replace(/</g, '&lt;')
826
+ .replace(/>/g, '&gt;')
827
+ .replace(/"/g, '&quot;');
828
+ };
829
+
830
+ /**
831
+ * Escapes "$" characters in replacement strings.
832
+ *
833
+ * @param {string} str
834
+ * @returns {string}
835
+ */
836
+ var escape_replace = function (str) {
837
+ return (str + '').replace(/\$/g, '$$$$');
838
+ };
839
+
840
+ var hook = {};
841
+
842
+ /**
843
+ * Wraps `method` on `self` so that `fn`
844
+ * is invoked before the original method.
845
+ *
846
+ * @param {object} self
847
+ * @param {string} method
848
+ * @param {function} fn
849
+ */
850
+ hook.before = function (self, method, fn) {
851
+ var original = self[method];
852
+ self[method] = function () {
853
+ fn.apply(self, arguments);
854
+ return original.apply(self, arguments);
855
+ };
856
+ };
857
+
858
+ /**
859
+ * Wraps `method` on `self` so that `fn`
860
+ * is invoked after the original method.
861
+ *
862
+ * @param {object} self
863
+ * @param {string} method
864
+ * @param {function} fn
865
+ */
866
+ hook.after = function (self, method, fn) {
867
+ var original = self[method];
868
+ self[method] = function () {
869
+ var result = original.apply(self, arguments);
870
+ fn.apply(self, arguments);
871
+ return result;
872
+ };
873
+ };
874
+
875
+ /**
876
+ * Wraps `fn` so that it can only be invoked once.
877
+ *
878
+ * @param {function} fn
879
+ * @returns {function}
880
+ */
881
+ var once = function (fn) {
882
+ var called = false;
883
+ return function () {
884
+ if (called) return;
885
+ called = true;
886
+ fn.apply(this, arguments);
887
+ };
888
+ };
889
+
890
+ /**
891
+ * Wraps `fn` so that it can only be called once
892
+ * every `delay` milliseconds (invoked on the falling edge).
893
+ *
894
+ * @param {function} fn
895
+ * @param {int} delay
896
+ * @returns {function}
897
+ */
898
+ var debounce = function (fn, delay) {
899
+ var timeout;
900
+ return function () {
901
+ var self = this;
902
+ var args = arguments;
903
+ window.clearTimeout(timeout);
904
+ timeout = window.setTimeout(function () {
905
+ fn.apply(self, args);
906
+ }, delay);
907
+ };
908
+ };
909
+
910
+ /**
911
+ * Debounce all fired events types listed in `types`
912
+ * while executing the provided `fn`.
913
+ *
914
+ * @param {object} self
915
+ * @param {array} types
916
+ * @param {function} fn
917
+ */
918
+ var debounce_events = function (self, types, fn) {
919
+ var type;
920
+ var trigger = self.trigger;
921
+ var event_args = {};
922
+
923
+ // override trigger method
924
+ self.trigger = function () {
925
+ var type = arguments[0];
926
+ if (types.indexOf(type) !== -1) {
927
+ event_args[type] = arguments;
928
+ } else {
929
+ return trigger.apply(self, arguments);
930
+ }
931
+ };
932
+
933
+ // invoke provided function
934
+ fn.apply(self, []);
935
+ self.trigger = trigger;
936
+
937
+ // trigger queued events
938
+ for (type in event_args) {
939
+ if (event_args.hasOwnProperty(type)) {
940
+ trigger.apply(self, event_args[type]);
941
+ }
942
+ }
943
+ };
944
+
945
+ /**
946
+ * A workaround for http://bugs.jquery.com/ticket/6696
947
+ *
948
+ * @param {object} $parent - Parent element to listen on.
949
+ * @param {string} event - Event name.
950
+ * @param {string} selector - Descendant selector to filter by.
951
+ * @param {function} fn - Event handler.
952
+ */
953
+ var watchChildEvent = function ($parent, event, selector, fn) {
954
+ $parent.on(event, selector, function (e) {
955
+ var child = e.target;
956
+ while (child && child.parentNode !== $parent[0]) {
957
+ child = child.parentNode;
958
+ }
959
+ e.currentTarget = child;
960
+ return fn.apply(this, [e]);
961
+ });
962
+ };
963
+
964
+ /**
965
+ * Determines the current selection within a text input control.
966
+ * Returns an object containing:
967
+ * - start
968
+ * - length
969
+ *
970
+ * @param {object} input
971
+ * @returns {object}
972
+ */
973
+ var getInputSelection = function (input) {
974
+ var result = {};
975
+ if (input === undefined) {
976
+ console.warn('WARN getInputSelection cannot locate input control');
977
+ return result;
978
+ }
979
+ if ('selectionStart' in input) {
980
+ result.start = input.selectionStart;
981
+ result.length = input.selectionEnd - result.start;
982
+ } else if (document.selection) {
983
+ input.focus();
984
+ var sel = document.selection.createRange();
985
+ var selLen = document.selection.createRange().text.length;
986
+ sel.moveStart('character', -input.value.length);
987
+ result.start = sel.text.length - selLen;
988
+ result.length = selLen;
989
+ }
990
+ return result;
991
+ };
992
+
993
+ /**
994
+ * Copies CSS properties from one element to another.
995
+ *
996
+ * @param {object} $from
997
+ * @param {object} $to
998
+ * @param {array} properties
999
+ */
1000
+ var transferStyles = function ($from, $to, properties) {
1001
+ var i, n, styles = {};
1002
+ if (properties) {
1003
+ for (i = 0, n = properties.length; i < n; i++) {
1004
+ styles[properties[i]] = $from.css(properties[i]);
1005
+ }
1006
+ } else {
1007
+ styles = $from.css();
1008
+ }
1009
+ $to.css(styles);
1010
+ };
1011
+
1012
+ /**
1013
+ * Measures the width of a string within a
1014
+ * parent element (in pixels).
1015
+ *
1016
+ * @param {string} str
1017
+ * @param {object} $parent
1018
+ * @returns {int}
1019
+ */
1020
+ var measureString = function (str, $parent) {
1021
+ if (!str) {
1022
+ return 0;
1023
+ }
1024
+
1025
+ if (!Selectize.$testInput) {
1026
+ Selectize.$testInput = $('<span />').css({
1027
+ position: 'absolute',
1028
+ width: 'auto',
1029
+ padding: 0,
1030
+ whiteSpace: 'pre'
1031
+ });
1032
+
1033
+ $('<div />').css({
1034
+ position: 'absolute',
1035
+ width: 0,
1036
+ height: 0,
1037
+ overflow: 'hidden'
1038
+ }).append(Selectize.$testInput).appendTo('body');
1039
+ }
1040
+
1041
+ Selectize.$testInput.text(str);
1042
+
1043
+ transferStyles($parent, Selectize.$testInput, [
1044
+ 'letterSpacing',
1045
+ 'fontSize',
1046
+ 'fontFamily',
1047
+ 'fontWeight',
1048
+ 'textTransform'
1049
+ ]);
1050
+
1051
+ return Selectize.$testInput.width();
1052
+ };
1053
+
1054
+ /**
1055
+ * Sets up an input to grow horizontally as the user
1056
+ * types. If the value is changed manually, you can
1057
+ * trigger the "update" handler to resize:
1058
+ *
1059
+ * $input.trigger('update');
1060
+ *
1061
+ * @param {object} $input
1062
+ */
1063
+ var autoGrow = function ($input) {
1064
+ var currentWidth = null;
1065
+
1066
+ var update = function (e, options) {
1067
+ var value, keyCode, printable, width;
1068
+ var placeholder, placeholderWidth;
1069
+ var shift, character, selection;
1070
+ e = e || window.event || {};
1071
+ options = options || {};
1072
+
1073
+ if (e.metaKey || e.altKey) return;
1074
+ if (!options.force && $input.data('grow') === false) return;
1075
+
1076
+ value = $input.val();
1077
+ if (e.type && e.type.toLowerCase() === 'keydown') {
1078
+ keyCode = e.keyCode;
1079
+ printable = (
1080
+ (keyCode >= 48 && keyCode <= 57) || // 0-9
1081
+ (keyCode >= 65 && keyCode <= 90) || // a-z
1082
+ (keyCode >= 96 && keyCode <= 111) || // numpad 0-9, numeric operators
1083
+ (keyCode >= 186 && keyCode <= 222) || // semicolon, equal, comma, dash, etc.
1084
+ keyCode === 32 // space
1085
+ );
1086
+
1087
+ if (keyCode === KEY_DELETE || keyCode === KEY_BACKSPACE) {
1088
+ selection = getInputSelection($input[0]);
1089
+ if (selection.length) {
1090
+ value = value.substring(0, selection.start) + value.substring(selection.start + selection.length);
1091
+ } else if (keyCode === KEY_BACKSPACE && selection.start) {
1092
+ value = value.substring(0, selection.start - 1) + value.substring(selection.start + 1);
1093
+ } else if (keyCode === KEY_DELETE && typeof selection.start !== 'undefined') {
1094
+ value = value.substring(0, selection.start) + value.substring(selection.start + 1);
1095
+ }
1096
+ } else if (printable) {
1097
+ shift = e.shiftKey;
1098
+ character = String.fromCharCode(e.keyCode);
1099
+ if (shift) character = character.toUpperCase();
1100
+ else character = character.toLowerCase();
1101
+ value += character;
1102
+ }
1103
+ }
1104
+
1105
+ placeholder = $input.attr('placeholder');
1106
+ if (placeholder) {
1107
+ placeholderWidth = measureString(placeholder, $input) + 4;
1108
+ } else {
1109
+ placeholderWidth = 0;
1110
+ }
1111
+
1112
+ width = Math.max(measureString(value, $input), placeholderWidth) + 4;
1113
+ if (width !== currentWidth) {
1114
+ currentWidth = width;
1115
+ $input.width(width);
1116
+ $input.triggerHandler('resize');
1117
+ }
1118
+ };
1119
+
1120
+ $input.on('keydown keyup update blur', update);
1121
+ update();
1122
+ };
1123
+
1124
+ var domToString = function (d) {
1125
+ var tmp = document.createElement('div');
1126
+
1127
+ tmp.appendChild(d.cloneNode(true));
1128
+
1129
+ return tmp.innerHTML;
1130
+ };
1131
+
1132
+ var logError = function (message, options) {
1133
+ if (!options) options = {};
1134
+ var component = "Selectize";
1135
+
1136
+ console.error(component + ": " + message)
1137
+
1138
+ if (options.explanation) {
1139
+ // console.group is undefined in <IE11
1140
+ if (console.group) console.group();
1141
+ console.error(options.explanation);
1142
+ if (console.group) console.groupEnd();
1143
+ }
1144
+ };
1145
+
1146
+ /**
1147
+ *
1148
+ * @param {any} data Data to testing
1149
+ * @returns {Boolean} true if is an JSON object
1150
+ */
1151
+ var isJSON = function (data) {
1152
+ try {
1153
+ JSON.parse(str);
1154
+ } catch (e) {
1155
+ return false;
1156
+ }
1157
+ return true;
1158
+ };
1159
+
1160
+ var Selectize = function($input, settings) {
1161
+ var key, i, n, dir, input, self = this;
1162
+ input = $input[0];
1163
+ input.selectize = self;
1164
+
1165
+ // detect rtl environment
1166
+ var computedStyle = window.getComputedStyle && window.getComputedStyle(input, null);
1167
+ dir = computedStyle ? computedStyle.getPropertyValue('direction') : input.currentStyle && input.currentStyle.direction;
1168
+ dir = dir || $input.parents('[dir]:first').attr('dir') || '';
1169
+
1170
+ // setup default state
1171
+ $.extend(self, {
1172
+ order : 0,
1173
+ settings : settings,
1174
+ $input : $input,
1175
+ tabIndex : $input.attr('tabindex') || '',
1176
+ tagType : input.tagName.toLowerCase() === 'select' ? TAG_SELECT : TAG_INPUT,
1177
+ rtl : /rtl/i.test(dir),
1178
+
1179
+ eventNS : '.selectize' + (++Selectize.count),
1180
+ highlightedValue : null,
1181
+ isBlurring : false,
1182
+ isOpen : false,
1183
+ isDisabled : false,
1184
+ isRequired : $input.is('[required]'),
1185
+ isInvalid : false,
1186
+ isLocked : false,
1187
+ isFocused : false,
1188
+ isInputHidden : false,
1189
+ isSetup : false,
1190
+ isShiftDown : false,
1191
+ isCmdDown : false,
1192
+ isCtrlDown : false,
1193
+ ignoreFocus : false,
1194
+ ignoreBlur : false,
1195
+ ignoreHover : false,
1196
+ hasOptions : false,
1197
+ currentResults : null,
1198
+ lastValue : '',
1199
+ lastValidValue : '',
1200
+ lastOpenTarget : false,
1201
+ caretPos : 0,
1202
+ loading : 0,
1203
+ loadedSearches : {},
1204
+ isDropdownClosing: false,
1205
+
1206
+ $activeOption : null,
1207
+ $activeItems : [],
1208
+
1209
+ optgroups : {},
1210
+ options : {},
1211
+ userOptions : {},
1212
+ items : [],
1213
+ renderCache : {},
1214
+ onSearchChange : settings.loadThrottle === null ? self.onSearchChange : debounce(self.onSearchChange, settings.loadThrottle)
1215
+ });
1216
+
1217
+ // search system
1218
+ self.sifter = new Sifter(this.options, {diacritics: settings.diacritics});
1219
+
1220
+ // build options table
1221
+ if (self.settings.options) {
1222
+ for (i = 0, n = self.settings.options.length; i < n; i++) {
1223
+ self.registerOption(self.settings.options[i]);
1224
+ }
1225
+ delete self.settings.options;
1226
+ }
1227
+
1228
+ // build optgroup table
1229
+ if (self.settings.optgroups) {
1230
+ for (i = 0, n = self.settings.optgroups.length; i < n; i++) {
1231
+ self.registerOptionGroup(self.settings.optgroups[i]);
1232
+ }
1233
+ delete self.settings.optgroups;
1234
+ }
1235
+
1236
+ // option-dependent defaults
1237
+ self.settings.mode = self.settings.mode || (self.settings.maxItems === 1 ? 'single' : 'multi');
1238
+ if (typeof self.settings.hideSelected !== 'boolean') {
1239
+ self.settings.hideSelected = self.settings.mode === 'multi';
1240
+ }
1241
+
1242
+ self.initializePlugins(self.settings.plugins);
1243
+ self.setupCallbacks();
1244
+ self.setupTemplates();
1245
+ self.setup();
1246
+ };
1247
+
1248
+ // mixins
1249
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1250
+
1251
+ MicroEvent.mixin(Selectize);
1252
+ MicroPlugin.mixin(Selectize);
1253
+
1254
+ // methods
1255
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1256
+
1257
+ $.extend(Selectize.prototype, {
1258
+
1259
+ /**
1260
+ * Creates all elements and sets up event bindings.
1261
+ */
1262
+ setup: function() {
1263
+ var self = this;
1264
+ var settings = self.settings;
1265
+ var eventNS = self.eventNS;
1266
+ var $window = $(window);
1267
+ var $document = $(document);
1268
+ var $input = self.$input;
1269
+
1270
+ var $wrapper;
1271
+ var $control;
1272
+ var $control_input;
1273
+ var $dropdown;
1274
+ var $dropdown_content;
1275
+ var $dropdown_parent;
1276
+ var inputMode;
1277
+ var timeout_blur;
1278
+ var timeout_focus;
1279
+ var classes;
1280
+ var classes_plugins;
1281
+ var inputId;
1282
+
1283
+ inputMode = self.settings.mode;
1284
+ classes = $input.attr('class') || '';
1285
+
1286
+ $wrapper = $('<div>').addClass(settings.wrapperClass).addClass(classes + ' selectize-control').addClass(inputMode);
1287
+ $control = $('<div>').addClass(settings.inputClass + ' selectize-input items').appendTo($wrapper);
1288
+ $control_input = $('<input type="select-one" autocomplete="new-password" autofill="no" />').appendTo($control).attr('tabindex', $input.is(':disabled') ? '-1' : self.tabIndex);
1289
+ $dropdown_parent = $(settings.dropdownParent || $wrapper);
1290
+ $dropdown = $('<div>').addClass(settings.dropdownClass).addClass(inputMode + ' selectize-dropdown').hide().appendTo($dropdown_parent);
1291
+ $dropdown_content = $('<div>').addClass(settings.dropdownContentClass + ' selectize-dropdown-content').attr('tabindex', '-1').appendTo($dropdown);
1292
+
1293
+ if(inputId = $input.attr('id')) {
1294
+ $control_input.attr('id', inputId + '-selectized');
1295
+ $("label[for='"+inputId+"']").attr('for', inputId + '-selectized');
1296
+ }
1297
+
1298
+ if(self.settings.copyClassesToDropdown) {
1299
+ $dropdown.addClass(classes);
1300
+ }
1301
+
1302
+ $wrapper.css({
1303
+ width: $input[0].style.width
1304
+ });
1305
+
1306
+ if (self.plugins.names.length) {
1307
+ classes_plugins = 'plugin-' + self.plugins.names.join(' plugin-');
1308
+ $wrapper.addClass(classes_plugins);
1309
+ $dropdown.addClass(classes_plugins);
1310
+ }
1311
+
1312
+ if ((settings.maxItems === null || settings.maxItems > 1) && self.tagType === TAG_SELECT) {
1313
+ $input.attr('multiple', 'multiple');
1314
+ }
1315
+
1316
+ if (self.settings.placeholder) {
1317
+ $control_input.attr('placeholder', settings.placeholder);
1318
+ }
1319
+
1320
+ // to have an identical rendering to a simple select (usefull for mobile device and do not open keyboard)
1321
+ if (!self.settings.search) {
1322
+ $control_input.attr('readonly', true);
1323
+ $control_input.attr('inputmode', 'none');
1324
+ $control.css('cursor', 'pointer');
1325
+ }
1326
+
1327
+ // if splitOn was not passed in, construct it from the delimiter to allow pasting universally
1328
+ if (!self.settings.splitOn && self.settings.delimiter) {
1329
+ var delimiterEscaped = self.settings.delimiter.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
1330
+ self.settings.splitOn = new RegExp('\\s*' + delimiterEscaped + '+\\s*');
1331
+ }
1332
+
1333
+ if ($input.attr('autocorrect')) {
1334
+ $control_input.attr('autocorrect', $input.attr('autocorrect'));
1335
+ }
1336
+
1337
+ if ($input.attr('autocapitalize')) {
1338
+ $control_input.attr('autocapitalize', $input.attr('autocapitalize'));
1339
+ }
1340
+ if ($input.is('input')) {
1341
+ $control_input[0].type = $input[0].type;
1342
+ }
1343
+
1344
+ self.$wrapper = $wrapper;
1345
+ self.$control = $control;
1346
+ self.$control_input = $control_input;
1347
+ self.$dropdown = $dropdown;
1348
+ self.$dropdown_content = $dropdown_content;
1349
+
1350
+ $dropdown.on('mouseenter mousedown mouseup click', '[data-disabled]>[data-selectable]', function(e) { e.stopImmediatePropagation(); });
1351
+ $dropdown.on('mouseenter', '[data-selectable]', function() { return self.onOptionHover.apply(self, arguments); });
1352
+ $dropdown.on('mouseup click', '[data-selectable]', function() { return self.onOptionSelect.apply(self, arguments); });
1353
+ watchChildEvent($control, 'mouseup', '*:not(input)', function() { return self.onItemSelect.apply(self, arguments); });
1354
+ autoGrow($control_input);
1355
+
1356
+ $control.on({
1357
+ mousedown : function() { return self.onMouseDown.apply(self, arguments); },
1358
+ click : function() { return self.onClick.apply(self, arguments); }
1359
+ });
1360
+
1361
+ $control_input.on({
1362
+ mousedown : function(e) {
1363
+ if (self.$control_input.val() !== '' || self.settings.openOnFocus) {
1364
+ e.stopPropagation();
1365
+ }
1366
+ },
1367
+ keydown : function() { return self.onKeyDown.apply(self, arguments); },
1368
+ keypress : function() { return self.onKeyPress.apply(self, arguments); },
1369
+ input : function() { return self.onInput.apply(self, arguments); },
1370
+ resize : function() { self.positionDropdown.apply(self, []); },
1371
+ // blur : function() { return self.onBlur.apply(self, arguments); },
1372
+ focus : function() { self.ignoreBlur = false; return self.onFocus.apply(self, arguments); },
1373
+ paste : function() { return self.onPaste.apply(self, arguments); }
1374
+ });
1375
+
1376
+ $document.on('keydown' + eventNS, function(e) {
1377
+ self.isCmdDown = e[IS_MAC ? 'metaKey' : 'ctrlKey'];
1378
+ self.isCtrlDown = e[IS_MAC ? 'altKey' : 'ctrlKey'];
1379
+ self.isShiftDown = e.shiftKey;
1380
+ });
1381
+
1382
+ $document.on('keyup' + eventNS, function(e) {
1383
+ if (e.keyCode === KEY_CTRL) self.isCtrlDown = false;
1384
+ if (e.keyCode === KEY_SHIFT) self.isShiftDown = false;
1385
+ if (e.keyCode === KEY_CMD) self.isCmdDown = false;
1386
+ });
1387
+
1388
+ $document.on('mousedown' + eventNS, function(e) {
1389
+ if (self.isFocused) {
1390
+ // prevent events on the dropdown scrollbar from causing the control to blur
1391
+ if (e.target === self.$dropdown[0] || e.target.parentNode === self.$dropdown[0]) {
1392
+ return false;
1393
+ }
1394
+ // blur on click outside
1395
+ // do not blur if the dropdown is clicked
1396
+ if (!self.$dropdown.has(e.target).length && e.target !== self.$control[0]) {
1397
+ self.blur(e.target);
1398
+ }
1399
+ }
1400
+ });
1401
+
1402
+ $window.on(['scroll' + eventNS, 'resize' + eventNS].join(' '), function() {
1403
+ if (self.isOpen) {
1404
+ self.positionDropdown.apply(self, arguments);
1405
+ }
1406
+ });
1407
+ $window.on('mousemove' + eventNS, function() {
1408
+ self.ignoreHover = self.settings.ignoreHover;
1409
+ });
1410
+
1411
+ // store original children and tab index so that they can be
1412
+ // restored when the destroy() method is called.
1413
+ // Detach children outside of DOM to prevent slowdown on large selects
1414
+ var inputPlaceholder = $('<div></div>');
1415
+ var inputChildren = $input.children().detach();
1416
+
1417
+ $input.replaceWith(inputPlaceholder);
1418
+ inputPlaceholder.replaceWith($input);
1419
+
1420
+ this.revertSettings = {
1421
+ $children : inputChildren,
1422
+ tabindex : $input.attr('tabindex')
1423
+ };
1424
+
1425
+ $input.attr('tabindex', -1).hide().after(self.$wrapper);
1426
+
1427
+ if (Array.isArray(settings.items)) {
1428
+ self.lastValidValue = settings.items;
1429
+ self.setValue(settings.items);
1430
+ delete settings.items;
1431
+ }
1432
+
1433
+ // feature detect for the validation API
1434
+ if (SUPPORTS_VALIDITY_API) {
1435
+ $input.on('invalid' + eventNS, function(e) {
1436
+ e.preventDefault();
1437
+ self.isInvalid = true;
1438
+ self.refreshState();
1439
+ });
1440
+ }
1441
+
1442
+ self.updateOriginalInput();
1443
+ self.refreshItems();
1444
+ self.refreshState();
1445
+ self.updatePlaceholder();
1446
+ self.isSetup = true;
1447
+
1448
+ if ($input.is(':disabled')) {
1449
+ self.disable();
1450
+ }
1451
+
1452
+ self.on('change', this.onChange);
1453
+
1454
+ $input.data('selectize', self);
1455
+ $input.addClass('selectized');
1456
+ self.trigger('initialize');
1457
+
1458
+ // preload options
1459
+ if (settings.preload === true) {
1460
+ self.onSearchChange('');
1461
+ }
1462
+
1463
+ },
1464
+
1465
+ /**
1466
+ * Sets up default rendering functions.
1467
+ */
1468
+ setupTemplates: function() {
1469
+ var self = this;
1470
+ var field_label = self.settings.labelField;
1471
+ var field_value = self.settings.valueField;
1472
+ var field_optgroup = self.settings.optgroupLabelField;
1473
+
1474
+ var templates = {
1475
+ 'optgroup': function(data) {
1476
+ return '<div class="optgroup">' + data.html + '</div>';
1477
+ },
1478
+ 'optgroup_header': function(data, escape) {
1479
+ return '<div class="optgroup-header">' + escape(data[field_optgroup]) + '</div>';
1480
+ },
1481
+ 'option': function(data, escape) {
1482
+ var classes = data.classes ? ' ' + data.classes : '';
1483
+ classes += data[field_value] === '' ? ' selectize-dropdown-emptyoptionlabel' : '';
1484
+
1485
+ var styles = data.styles ? ' style="' + data.styles + '"': '';
1486
+ return '<div' + styles + ' class="option' + classes + '">' + escape(data[field_label]) + '</div>';
1487
+ },
1488
+ 'item': function(data, escape) {
1489
+ return '<div class="item">' + escape(data[field_label]) + '</div>';
1490
+ },
1491
+ 'option_create': function(data, escape) {
1492
+ return '<div class="create">Add <strong>' + escape(data.input) + '</strong>&#x2026;</div>';
1493
+ }
1494
+ };
1495
+
1496
+ self.settings.render = $.extend({}, templates, self.settings.render);
1497
+ },
1498
+
1499
+ /**
1500
+ * Maps fired events to callbacks provided
1501
+ * in the settings used when creating the control.
1502
+ */
1503
+ setupCallbacks: function() {
1504
+ var key, fn, callbacks = {
1505
+ 'initialize' : 'onInitialize',
1506
+ 'change' : 'onChange',
1507
+ 'item_add' : 'onItemAdd',
1508
+ 'item_remove' : 'onItemRemove',
1509
+ 'clear' : 'onClear',
1510
+ 'option_add' : 'onOptionAdd',
1511
+ 'option_remove' : 'onOptionRemove',
1512
+ 'option_clear' : 'onOptionClear',
1513
+ 'optgroup_add' : 'onOptionGroupAdd',
1514
+ 'optgroup_remove' : 'onOptionGroupRemove',
1515
+ 'optgroup_clear' : 'onOptionGroupClear',
1516
+ 'dropdown_open' : 'onDropdownOpen',
1517
+ 'dropdown_close' : 'onDropdownClose',
1518
+ 'type' : 'onType',
1519
+ 'load' : 'onLoad',
1520
+ 'focus' : 'onFocus',
1521
+ 'blur' : 'onBlur',
1522
+ 'dropdown_item_activate' : 'onDropdownItemActivate',
1523
+ 'dropdown_item_deactivate' : 'onDropdownItemDeactivate'
1524
+ };
1525
+
1526
+ for (key in callbacks) {
1527
+ if (callbacks.hasOwnProperty(key)) {
1528
+ fn = this.settings[callbacks[key]];
1529
+ if (fn) this.on(key, fn);
1530
+ }
1531
+ }
1532
+ },
1533
+
1534
+ /**
1535
+ * Triggered when the main control element
1536
+ * has a click event.
1537
+ *
1538
+ * @param {PointerEvent} e
1539
+ * @return {boolean}
1540
+ */
1541
+ onClick: function(e) {
1542
+ var self = this;
1543
+
1544
+ // if the dropdown is closing due to a mousedown, we don't want to
1545
+ // refocus the element.
1546
+ if (self.isDropdownClosing) {
1547
+ return;
1548
+ }
1549
+
1550
+ // necessary for mobile webkit devices (manual focus triggering
1551
+ // is ignored unless invoked within a click event)
1552
+ // also necessary to reopen a dropdown that has been closed by
1553
+ // closeAfterSelect
1554
+ if (!self.isFocused || !self.isOpen) {
1555
+ self.focus();
1556
+ e.preventDefault();
1557
+ }
1558
+ },
1559
+
1560
+ /**
1561
+ * Triggered when the main control element
1562
+ * has a mouse down event.
1563
+ *
1564
+ * @param {object} e
1565
+ * @return {boolean}
1566
+ */
1567
+ onMouseDown: function(e) {
1568
+ var self = this;
1569
+ var defaultPrevented = e.isDefaultPrevented();
1570
+ var $target = $(e.target);
1571
+
1572
+ if (!self.isFocused) {
1573
+ // give control focus
1574
+ if (!defaultPrevented) {
1575
+ window.setTimeout(function() {
1576
+ self.focus();
1577
+ }, 0);
1578
+ }
1579
+ }
1580
+ // retain focus by preventing native handling. if the
1581
+ // event target is the input it should not be modified.
1582
+ // otherwise, text selection within the input won't work.
1583
+ if (e.target !== self.$control_input[0] || self.$control_input.val() === '') {
1584
+ if (self.settings.mode === 'single') {
1585
+ // toggle dropdown
1586
+ self.isOpen ? self.close() : self.open();
1587
+ } else {
1588
+ if (!defaultPrevented) {
1589
+ self.setActiveItem(null);
1590
+ }
1591
+ if (!self.settings.openOnFocus) {
1592
+ if (self.isOpen && e.target === self.lastOpenTarget) {
1593
+ self.close();
1594
+ self.lastOpenTarget = false;
1595
+ } else if (!self.isOpen) {
1596
+ self.refreshOptions();
1597
+ self.open();
1598
+ self.lastOpenTarget = e.target;
1599
+ } else {
1600
+ self.lastOpenTarget = e.target;
1601
+ }
1602
+ }
1603
+ }
1604
+ return false;
1605
+ }
1606
+ },
1607
+
1608
+ /**
1609
+ * Triggered when the value of the control has been changed.
1610
+ * This should propagate the event to the original DOM
1611
+ * input / select element.
1612
+ */
1613
+ onChange: function() {
1614
+ var self = this;
1615
+ if (self.getValue() !== "") {
1616
+ self.lastValidValue = self.getValue();
1617
+ }
1618
+ this.$input.trigger('input');
1619
+ this.$input.trigger('change');
1620
+ },
1621
+
1622
+ /**
1623
+ * Triggered on <input> paste.
1624
+ *
1625
+ * @param {object} e
1626
+ * @returns {boolean}
1627
+ */
1628
+ onPaste: function(e) {
1629
+ var self = this;
1630
+
1631
+ if (self.isFull() || self.isInputHidden || self.isLocked) {
1632
+ e.preventDefault();
1633
+ return;
1634
+ }
1635
+
1636
+ // If a regex or string is included, this will split the pasted
1637
+ // input and create Items for each separate value
1638
+ if (self.settings.splitOn) {
1639
+
1640
+ // Wait for pasted text to be recognized in value
1641
+ setTimeout(function() {
1642
+ var pastedText = self.$control_input.val();
1643
+ if(!pastedText.match(self.settings.splitOn)){ return }
1644
+
1645
+ var splitInput = pastedText
1646
+ .trim()
1647
+ .split(self.settings.splitOn);
1648
+ for (var i = 0, n = splitInput.length; i < n; i++) {
1649
+ self.createItem(splitInput[i]);
1650
+ }
1651
+ }, 0);
1652
+ }
1653
+ },
1654
+
1655
+ /**
1656
+ * Triggered on <input> keypress.
1657
+ *
1658
+ * @param {object} e
1659
+ * @returns {boolean}
1660
+ */
1661
+ onKeyPress: function(e) {
1662
+ if (this.isLocked) return e && e.preventDefault();
1663
+ var character = String.fromCharCode(e.keyCode || e.which);
1664
+ if (this.settings.create && this.settings.mode === 'multi' && character === this.settings.delimiter) {
1665
+ this.createItem();
1666
+ e.preventDefault();
1667
+ return false;
1668
+ }
1669
+ },
1670
+
1671
+ /**
1672
+ * Triggered on <input> keydown.
1673
+ *
1674
+ * @param {object} e
1675
+ * @returns {boolean}
1676
+ */
1677
+ onKeyDown: function(e) {
1678
+ var isInput = e.target === this.$control_input[0];
1679
+ var self = this;
1680
+
1681
+ if (self.isLocked) {
1682
+ if (e.keyCode !== KEY_TAB) {
1683
+ e.preventDefault();
1684
+ }
1685
+ return;
1686
+ }
1687
+
1688
+ switch (e.keyCode) {
1689
+ case KEY_A:
1690
+ if (self.isCmdDown) {
1691
+ self.selectAll();
1692
+ return;
1693
+ }
1694
+ break;
1695
+ case KEY_ESC:
1696
+ if (self.isOpen) {
1697
+ e.preventDefault();
1698
+ e.stopPropagation();
1699
+ self.close();
1700
+ }
1701
+ return;
1702
+ case KEY_N:
1703
+ if (!e.ctrlKey || e.altKey) break;
1704
+ case KEY_DOWN:
1705
+ if (!self.isOpen && self.hasOptions) {
1706
+ self.open();
1707
+ } else if (self.$activeOption) {
1708
+ self.ignoreHover = true;
1709
+ var $next = self.getAdjacentOption(self.$activeOption, 1);
1710
+ if ($next.length) self.setActiveOption($next, true, true);
1711
+ }
1712
+ e.preventDefault();
1713
+ return;
1714
+ case KEY_P:
1715
+ if (!e.ctrlKey || e.altKey) break;
1716
+ case KEY_UP:
1717
+ if (self.$activeOption) {
1718
+ self.ignoreHover = true;
1719
+ var $prev = self.getAdjacentOption(self.$activeOption, -1);
1720
+ if ($prev.length) self.setActiveOption($prev, true, true);
1721
+ }
1722
+ e.preventDefault();
1723
+ return;
1724
+ case KEY_RETURN:
1725
+ if (self.isOpen && self.$activeOption) {
1726
+ self.onOptionSelect({currentTarget: self.$activeOption});
1727
+ e.preventDefault();
1728
+ }
1729
+ return;
1730
+ case KEY_LEFT:
1731
+ self.advanceSelection(-1, e);
1732
+ return;
1733
+ case KEY_RIGHT:
1734
+ self.advanceSelection(1, e);
1735
+ return;
1736
+ case KEY_TAB:
1737
+ if (self.settings.selectOnTab && self.isOpen && self.$activeOption) {
1738
+ self.onOptionSelect({currentTarget: self.$activeOption});
1739
+
1740
+ // Default behaviour is to jump to the next field, we only want this
1741
+ // if the current field doesn't accept any more entries
1742
+ if (!self.isFull()) {
1743
+ e.preventDefault();
1744
+ }
1745
+ }
1746
+ if (self.settings.create && self.createItem() && self.settings.showAddOptionOnCreate) {
1747
+ e.preventDefault();
1748
+ }
1749
+ return;
1750
+ case KEY_BACKSPACE:
1751
+ case KEY_DELETE:
1752
+ self.deleteSelection(e);
1753
+ return;
1754
+ }
1755
+
1756
+ if ((self.isFull() || self.isInputHidden) && !(IS_MAC ? e.metaKey : e.ctrlKey)) {
1757
+ e.preventDefault();
1758
+ return;
1759
+ }
1760
+ },
1761
+
1762
+ /**
1763
+ * Triggered on <input> input.
1764
+ *
1765
+ * @param {object} e
1766
+ * @returns {boolean}
1767
+ */
1768
+ onInput: function(e) {
1769
+ var self = this;
1770
+
1771
+ var value = self.$control_input.val() || '';
1772
+ if (self.lastValue !== value) {
1773
+ self.lastValue = value;
1774
+ self.onSearchChange(value);
1775
+ self.refreshOptions();
1776
+ self.trigger('type', value);
1777
+ }
1778
+ },
1779
+
1780
+ /**
1781
+ * Invokes the user-provide option provider / loader.
1782
+ *
1783
+ * Note: this function is debounced in the Selectize
1784
+ * constructor (by `settings.loadThrottle` milliseconds)
1785
+ *
1786
+ * @param {string} value
1787
+ */
1788
+ onSearchChange: function(value) {
1789
+ var self = this;
1790
+ var fn = self.settings.load;
1791
+ if (!fn) return;
1792
+ if (self.loadedSearches.hasOwnProperty(value)) return;
1793
+ self.loadedSearches[value] = true;
1794
+ self.load(function(callback) {
1795
+ fn.apply(self, [value, callback]);
1796
+ });
1797
+ },
1798
+
1799
+ /**
1800
+ * Triggered on <input> focus.
1801
+ *
1802
+ * @param {FocusEvent} e (optional)
1803
+ * @returns {boolean}
1804
+ */
1805
+ onFocus: function(e) {
1806
+ var self = this;
1807
+ var wasFocused = self.isFocused;
1808
+
1809
+ if (self.isDisabled) {
1810
+ self.blur();
1811
+ e && e.preventDefault();
1812
+ return false;
1813
+ }
1814
+
1815
+ if (self.ignoreFocus) return;
1816
+ self.isFocused = true;
1817
+ if (self.settings.preload === 'focus') self.onSearchChange('');
1818
+
1819
+ if (!wasFocused) self.trigger('focus');
1820
+
1821
+ if (!self.$activeItems.length) {
1822
+ self.showInput();
1823
+ self.setActiveItem(null);
1824
+ self.refreshOptions(!!self.settings.openOnFocus);
1825
+ }
1826
+
1827
+ self.refreshState();
1828
+ },
1829
+
1830
+ /**
1831
+ * Triggered on <input> blur.
1832
+ *
1833
+ * @param {object} e
1834
+ * @param {Element} dest
1835
+ */
1836
+ onBlur: function(e, dest) {
1837
+ var self = this;
1838
+ if (!self.isFocused) return;
1839
+ self.isFocused = false;
1840
+
1841
+ if (self.ignoreFocus) {
1842
+ return;
1843
+ }
1844
+ // Bug fix do not blur dropdown here
1845
+ // else if (!self.ignoreBlur && document.activeElement === self.$dropdown_content[0]) {
1846
+ // // necessary to prevent IE closing the dropdown when the scrollbar is clicked
1847
+ // self.ignoreBlur = true;
1848
+ // self.onFocus(e);
1849
+ // return;
1850
+ // }
1851
+
1852
+ var deactivate = function() {
1853
+ self.close();
1854
+ self.setTextboxValue('');
1855
+ self.setActiveItem(null);
1856
+ self.setActiveOption(null);
1857
+ self.setCaret(self.items.length);
1858
+ self.refreshState();
1859
+
1860
+ // IE11 bug: element still marked as active
1861
+ dest && dest.focus && dest.focus();
1862
+
1863
+ self.isBlurring = false;
1864
+ self.ignoreFocus = false;
1865
+ self.trigger('blur');
1866
+ };
1867
+
1868
+ self.isBlurring = true;
1869
+ self.ignoreFocus = true;
1870
+ if (self.settings.create && self.settings.createOnBlur) {
1871
+ self.createItem(null, false, deactivate);
1872
+ } else {
1873
+ deactivate();
1874
+ }
1875
+ },
1876
+
1877
+ /**
1878
+ * Triggered when the user rolls over
1879
+ * an option in the autocomplete dropdown menu.
1880
+ *
1881
+ * @param {object} e
1882
+ * @returns {boolean}
1883
+ */
1884
+ onOptionHover: function(e) {
1885
+ if (this.ignoreHover) return;
1886
+ this.setActiveOption(e.currentTarget, false);
1887
+ },
1888
+
1889
+ /**
1890
+ * Triggered when the user clicks on an option
1891
+ * in the autocomplete dropdown menu.
1892
+ *
1893
+ * @param {object} e
1894
+ * @returns {boolean}
1895
+ */
1896
+ onOptionSelect: function(e) {
1897
+ var value, $target, $option, self = this;
1898
+
1899
+ if (e.preventDefault) {
1900
+ e.preventDefault();
1901
+ e.stopPropagation();
1902
+ }
1903
+
1904
+ $target = $(e.currentTarget);
1905
+ if ($target.hasClass('create')) {
1906
+ self.createItem(null, function() {
1907
+ if (self.settings.closeAfterSelect) {
1908
+ self.close();
1909
+ }
1910
+ });
1911
+ } else {
1912
+ value = $target.attr('data-value');
1913
+ if (typeof value !== 'undefined') {
1914
+ self.lastQuery = null;
1915
+ self.setTextboxValue('');
1916
+ self.addItem(value);
1917
+ if (self.settings.closeAfterSelect) {
1918
+ self.close();
1919
+ } else if (!self.settings.hideSelected && e.type && /mouse/.test(e.type)) {
1920
+ self.setActiveOption(self.getOption(value));
1921
+ }
1922
+ }
1923
+ }
1924
+ },
1925
+
1926
+ /**
1927
+ * Triggered when the user clicks on an item
1928
+ * that has been selected.
1929
+ *
1930
+ * @param {object} e
1931
+ * @returns {boolean}
1932
+ */
1933
+ onItemSelect: function(e) {
1934
+ var self = this;
1935
+
1936
+ if (self.isLocked) return;
1937
+ if (self.settings.mode === 'multi') {
1938
+ e.preventDefault();
1939
+ self.setActiveItem(e.currentTarget, e);
1940
+ }
1941
+ },
1942
+
1943
+ /**
1944
+ * Invokes the provided method that provides
1945
+ * results to a callback---which are then added
1946
+ * as options to the control.
1947
+ *
1948
+ * @param {function} fn
1949
+ */
1950
+ load: function(fn) {
1951
+ var self = this;
1952
+ var $wrapper = self.$wrapper.addClass(self.settings.loadingClass);
1953
+
1954
+ self.loading++;
1955
+ fn.apply(self, [function(results) {
1956
+ self.loading = Math.max(self.loading - 1, 0);
1957
+ if (results && results.length) {
1958
+ self.addOption(results);
1959
+ self.refreshOptions(self.isFocused && !self.isInputHidden);
1960
+ }
1961
+ if (!self.loading) {
1962
+ $wrapper.removeClass(self.settings.loadingClass);
1963
+ }
1964
+ self.trigger('load', results);
1965
+ }]);
1966
+ },
1967
+
1968
+ /**
1969
+ * Gets the value of input field of the control.
1970
+ *
1971
+ * @returns {string} value
1972
+ */
1973
+ getTextboxValue: function() {
1974
+ var $input = this.$control_input;
1975
+ return $input.val();
1976
+ },
1977
+
1978
+ /**
1979
+ * Sets the input field of the control to the specified value.
1980
+ *
1981
+ * @param {string} value
1982
+ */
1983
+ setTextboxValue: function(value) {
1984
+ var $input = this.$control_input;
1985
+ var changed = $input.val() !== value;
1986
+ if (changed) {
1987
+ $input.val(value).triggerHandler('update');
1988
+ this.lastValue = value;
1989
+ }
1990
+ },
1991
+
1992
+ /**
1993
+ * Returns the value of the control. If multiple items
1994
+ * can be selected (e.g. <select multiple>), this returns
1995
+ * an array. If only one item can be selected, this
1996
+ * returns a string.
1997
+ *
1998
+ * @returns {mixed}
1999
+ */
2000
+ getValue: function() {
2001
+ if (this.tagType === TAG_SELECT && this.$input.attr('multiple')) {
2002
+ return this.items;
2003
+ } else {
2004
+ return this.items.join(this.settings.delimiter);
2005
+ }
2006
+ },
2007
+
2008
+ /**
2009
+ * Resets the selected items to the given value.
2010
+ *
2011
+ * @param {Array<String|Number>} value
2012
+ */
2013
+ setValue: function(value, silent) {
2014
+ const items = Array.isArray(value) ? value : [value];
2015
+ if (items.join('') === this.items.join('')) {
2016
+ return;
2017
+ }
2018
+
2019
+ var events = silent ? [] : ['change'];
2020
+
2021
+ debounce_events(this, events, function() {
2022
+ this.clear(silent);
2023
+ this.addItems(value, silent);
2024
+ });
2025
+ },
2026
+
2027
+ /**
2028
+ * Resets the number of max items to the given value
2029
+ *
2030
+ * @param {number} value
2031
+ */
2032
+ setMaxItems: function(value){
2033
+ if(value === 0) value = null; //reset to unlimited items.
2034
+ this.settings.maxItems = value;
2035
+ this.settings.mode = this.settings.mode || (this.settings.maxItems === 1 ? 'single' : 'multi');
2036
+ this.refreshState();
2037
+ },
2038
+
2039
+ /**
2040
+ * Sets the selected item.
2041
+ *
2042
+ * @param {object} $item
2043
+ * @param {object} e (optional)
2044
+ */
2045
+ setActiveItem: function($item, e) {
2046
+ var self = this;
2047
+ var eventName;
2048
+ var i, idx, begin, end, item, swap;
2049
+ var $last;
2050
+
2051
+ if (self.settings.mode === 'single') return;
2052
+ $item = $($item);
2053
+
2054
+ // clear the active selection
2055
+ if (!$item.length) {
2056
+ $(self.$activeItems).removeClass('active');
2057
+ self.$activeItems = [];
2058
+ if (self.isFocused) {
2059
+ self.showInput();
2060
+ }
2061
+ return;
2062
+ }
2063
+
2064
+ // modify selection
2065
+ eventName = e && e.type.toLowerCase();
2066
+
2067
+ if (eventName === 'mousedown' && self.isShiftDown && self.$activeItems.length) {
2068
+ $last = self.$control.children('.active:last');
2069
+ begin = Array.prototype.indexOf.apply(self.$control[0].childNodes, [$last[0]]);
2070
+ end = Array.prototype.indexOf.apply(self.$control[0].childNodes, [$item[0]]);
2071
+ if (begin > end) {
2072
+ swap = begin;
2073
+ begin = end;
2074
+ end = swap;
2075
+ }
2076
+ for (i = begin; i <= end; i++) {
2077
+ item = self.$control[0].childNodes[i];
2078
+ if (self.$activeItems.indexOf(item) === -1) {
2079
+ $(item).addClass('active');
2080
+ self.$activeItems.push(item);
2081
+ }
2082
+ }
2083
+ e.preventDefault();
2084
+ } else if ((eventName === 'mousedown' && self.isCtrlDown) || (eventName === 'keydown' && this.isShiftDown)) {
2085
+ if ($item.hasClass('active')) {
2086
+ idx = self.$activeItems.indexOf($item[0]);
2087
+ self.$activeItems.splice(idx, 1);
2088
+ $item.removeClass('active');
2089
+ } else {
2090
+ self.$activeItems.push($item.addClass('active')[0]);
2091
+ }
2092
+ } else {
2093
+ $(self.$activeItems).removeClass('active');
2094
+ self.$activeItems = [$item.addClass('active')[0]];
2095
+ }
2096
+
2097
+ // ensure control has focus
2098
+ self.hideInput();
2099
+ if (!this.isFocused) {
2100
+ self.focus();
2101
+ }
2102
+ },
2103
+
2104
+ /**
2105
+ * Sets the selected item in the dropdown menu
2106
+ * of available options.
2107
+ *
2108
+ * @param {object} $object
2109
+ * @param {boolean} scroll
2110
+ * @param {boolean} animate
2111
+ */
2112
+ setActiveOption: function($option, scroll, animate) {
2113
+ var height_menu, height_item, y;
2114
+ var scroll_top, scroll_bottom;
2115
+ var self = this;
2116
+
2117
+ if (self.$activeOption) {
2118
+ self.$activeOption.removeClass('active');
2119
+ self.trigger('dropdown_item_deactivate', self.$activeOption.attr('data-value'));
2120
+ }
2121
+ self.$activeOption = null;
2122
+
2123
+ $option = $($option);
2124
+ if (!$option.length) return;
2125
+
2126
+ self.$activeOption = $option.addClass('active');
2127
+ if (self.isOpen) self.trigger('dropdown_item_activate', self.$activeOption.attr('data-value'));
2128
+
2129
+ if (scroll || !isset(scroll)) {
2130
+
2131
+ height_menu = self.$dropdown_content.height();
2132
+ height_item = self.$activeOption.outerHeight(true);
2133
+ scroll = self.$dropdown_content.scrollTop() || 0;
2134
+ y = self.$activeOption.offset().top - self.$dropdown_content.offset().top + scroll;
2135
+ scroll_top = y;
2136
+ scroll_bottom = y - height_menu + height_item;
2137
+
2138
+ if (y + height_item > height_menu + scroll) {
2139
+ self.$dropdown_content.stop().animate({scrollTop: scroll_bottom}, animate ? self.settings.scrollDuration : 0);
2140
+ } else if (y < scroll) {
2141
+ self.$dropdown_content.stop().animate({scrollTop: scroll_top}, animate ? self.settings.scrollDuration : 0);
2142
+ }
2143
+
2144
+ }
2145
+ },
2146
+
2147
+ /**
2148
+ * Selects all items (CTRL + A).
2149
+ */
2150
+ selectAll: function() {
2151
+ var self = this;
2152
+ if (self.settings.mode === 'single') return;
2153
+
2154
+ self.$activeItems = Array.prototype.slice.apply(self.$control.children(':not(input)').addClass('active'));
2155
+ if (self.$activeItems.length) {
2156
+ self.hideInput();
2157
+ self.close();
2158
+ }
2159
+ self.focus();
2160
+ },
2161
+
2162
+ /**
2163
+ * Hides the input element out of view, while
2164
+ * retaining its focus.
2165
+ */
2166
+ hideInput: function() {
2167
+ var self = this;
2168
+
2169
+ self.setTextboxValue('');
2170
+ self.$control_input.css({opacity: 0, position: 'absolute', left: self.rtl ? 10000 : 0});
2171
+ self.isInputHidden = true;
2172
+ },
2173
+
2174
+ /**
2175
+ * Restores input visibility.
2176
+ */
2177
+ showInput: function() {
2178
+ this.$control_input.css({opacity: 1, position: 'relative', left: 0});
2179
+ this.isInputHidden = false;
2180
+ },
2181
+
2182
+ /**
2183
+ * Gives the control focus.
2184
+ */
2185
+ focus: function() {
2186
+ var self = this;
2187
+ if (self.isDisabled) return self;
2188
+
2189
+ self.ignoreFocus = true;
2190
+ self.$control_input[0].focus();
2191
+ window.setTimeout(function() {
2192
+ self.ignoreFocus = false;
2193
+ self.onFocus();
2194
+ }, 0);
2195
+ return self;
2196
+ },
2197
+
2198
+ /**
2199
+ * Forces the control out of focus.
2200
+ *
2201
+ * @param {Element} dest
2202
+ */
2203
+ blur: function(dest) {
2204
+ this.$control_input[0].blur();
2205
+ this.onBlur(null, dest);
2206
+ return this;
2207
+ },
2208
+
2209
+ /**
2210
+ * Returns a function that scores an object
2211
+ * to show how good of a match it is to the
2212
+ * provided query.
2213
+ *
2214
+ * @param {string} query
2215
+ * @param {object} options
2216
+ * @return {function}
2217
+ */
2218
+ getScoreFunction: function(query) {
2219
+ return this.sifter.getScoreFunction(query, this.getSearchOptions());
2220
+ },
2221
+
2222
+ /**
2223
+ * Returns search options for sifter (the system
2224
+ * for scoring and sorting results).
2225
+ *
2226
+ * @see https://github.com/brianreavis/sifter.js
2227
+ * @return {object}
2228
+ */
2229
+ getSearchOptions: function() {
2230
+ var settings = this.settings;
2231
+ var sort = settings.sortField;
2232
+ if (typeof sort === 'string') {
2233
+ sort = [{field: sort}];
2234
+ }
2235
+
2236
+ return {
2237
+ fields : settings.searchField,
2238
+ conjunction : settings.searchConjunction,
2239
+ sort : sort,
2240
+ nesting : settings.nesting,
2241
+ filter : settings.filter,
2242
+ respect_word_boundaries : settings.respect_word_boundaries
2243
+ };
2244
+ },
2245
+
2246
+ /**
2247
+ * Searches through available options and returns
2248
+ * a sorted array of matches.
2249
+ *
2250
+ * Returns an object containing:
2251
+ *
2252
+ * - query {string}
2253
+ * - tokens {array}
2254
+ * - total {int}
2255
+ * - items {array}
2256
+ *
2257
+ * @param {string} query
2258
+ * @returns {object}
2259
+ */
2260
+ search: function(query) {
2261
+ var i, value, score, result, calculateScore;
2262
+ var self = this;
2263
+ var settings = self.settings;
2264
+ var options = this.getSearchOptions();
2265
+
2266
+ // validate user-provided result scoring function
2267
+ if (settings.score) {
2268
+ calculateScore = self.settings.score.apply(this, [query]);
2269
+ if (typeof calculateScore !== 'function') {
2270
+ throw new Error('Selectize "score" setting must be a function that returns a function');
2271
+ }
2272
+ }
2273
+
2274
+ // perform search
2275
+ if (query !== self.lastQuery) {
2276
+ if (settings.normalize) query = query.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
2277
+ self.lastQuery = query;
2278
+ result = self.sifter.search(query, $.extend(options, {score: calculateScore}));
2279
+ self.currentResults = result;
2280
+ } else {
2281
+ result = $.extend(true, {}, self.currentResults);
2282
+ }
2283
+
2284
+ // filter out selected items
2285
+ if (settings.hideSelected) {
2286
+ for (i = result.items.length - 1; i >= 0; i--) {
2287
+ if (self.items.indexOf(hash_key(result.items[i].id)) !== -1) {
2288
+ result.items.splice(i, 1);
2289
+ }
2290
+ }
2291
+ }
2292
+
2293
+ return result;
2294
+ },
2295
+
2296
+ /**
2297
+ * Refreshes the list of available options shown
2298
+ * in the autocomplete dropdown menu.
2299
+ *
2300
+ * @param {boolean} triggerDropdown
2301
+ */
2302
+ refreshOptions: function(triggerDropdown) {
2303
+ var i, j, k, n, groups, groups_order, option, option_html, optgroup, optgroups, html, html_children, has_create_option;
2304
+ var $active, $active_before, $create;
2305
+
2306
+ if (typeof triggerDropdown === 'undefined') {
2307
+ triggerDropdown = true;
2308
+ }
2309
+
2310
+ var self = this;
2311
+ var query = (self.$control_input.val()).trim();
2312
+ var results = self.search(query);
2313
+ var $dropdown_content = self.$dropdown_content;
2314
+ var active_before = self.$activeOption && hash_key(self.$activeOption.attr('data-value'));
2315
+
2316
+ // build markup
2317
+ n = results.items.length;
2318
+ if (typeof self.settings.maxOptions === 'number') {
2319
+ n = Math.min(n, self.settings.maxOptions);
2320
+ }
2321
+
2322
+ // render and group available options individually
2323
+ groups = {};
2324
+ groups_order = [];
2325
+
2326
+ for (i = 0; i < n; i++) {
2327
+ option = self.options[results.items[i].id];
2328
+ option_html = self.render('option', option);
2329
+ optgroup = option[self.settings.optgroupField] || '';
2330
+ optgroups = Array.isArray(optgroup) ? optgroup : [optgroup];
2331
+
2332
+ for (j = 0, k = optgroups && optgroups.length; j < k; j++) {
2333
+ optgroup = optgroups[j];
2334
+ if (!self.optgroups.hasOwnProperty(optgroup) && typeof self.settings.optionGroupRegister === 'function') {
2335
+ var regGroup;
2336
+ if (regGroup = self.settings.optionGroupRegister.apply(self, [optgroup])) {
2337
+ self.registerOptionGroup(regGroup);
2338
+ }
2339
+ }
2340
+ if (!self.optgroups.hasOwnProperty(optgroup)) {
2341
+ optgroup = '';
2342
+ }
2343
+ if (!groups.hasOwnProperty(optgroup)) {
2344
+ groups[optgroup] = document.createDocumentFragment();
2345
+ groups_order.push(optgroup);
2346
+ }
2347
+ groups[optgroup].appendChild(option_html);
2348
+ }
2349
+ }
2350
+
2351
+ // sort optgroups
2352
+ if (this.settings.lockOptgroupOrder) {
2353
+ groups_order.sort(function(a, b) {
2354
+ var a_order = self.optgroups[a] && self.optgroups[a].$order || 0;
2355
+ var b_order = self.optgroups[b] && self.optgroups[b].$order || 0;
2356
+ return a_order - b_order;
2357
+ });
2358
+ }
2359
+
2360
+ // render optgroup headers & join groups
2361
+ html = document.createDocumentFragment();
2362
+ for (i = 0, n = groups_order.length; i < n; i++) {
2363
+ optgroup = groups_order[i];
2364
+ if (self.optgroups.hasOwnProperty(optgroup) && groups[optgroup].childNodes.length) {
2365
+ // render the optgroup header and options within it,
2366
+ // then pass it to the wrapper template
2367
+ html_children = document.createDocumentFragment();
2368
+ html_children.appendChild(self.render('optgroup_header', self.optgroups[optgroup]));
2369
+ html_children.appendChild(groups[optgroup]);
2370
+
2371
+ html.appendChild(self.render('optgroup', $.extend({}, self.optgroups[optgroup], {
2372
+ html: domToString(html_children),
2373
+ dom: html_children
2374
+ })));
2375
+ } else {
2376
+ html.appendChild(groups[optgroup]);
2377
+ }
2378
+ }
2379
+
2380
+ $dropdown_content.html(html);
2381
+
2382
+ // highlight matching terms inline
2383
+ if (self.settings.highlight) {
2384
+ $dropdown_content.removeHighlight();
2385
+ if (results.query.length && results.tokens.length) {
2386
+ for (i = 0, n = results.tokens.length; i < n; i++) {
2387
+ highlight($dropdown_content, results.tokens[i].regex);
2388
+ }
2389
+ }
2390
+ }
2391
+
2392
+ // add "selected" class to selected options
2393
+ if (!self.settings.hideSelected) {
2394
+ // clear selection on all previously selected elements first
2395
+ self.$dropdown.find('.selected').removeClass('selected');
2396
+
2397
+ for (i = 0, n = self.items.length; i < n; i++) {
2398
+ self.getOption(self.items[i]).addClass('selected');
2399
+ }
2400
+ }
2401
+
2402
+ if (self.settings.dropdownSize.sizeType !== 'auto' && self.isOpen) {
2403
+ self.setupDropdownHeight();
2404
+ }
2405
+
2406
+ // add create option
2407
+ has_create_option = self.canCreate(query);
2408
+ if (has_create_option) {
2409
+ if(self.settings.showAddOptionOnCreate) {
2410
+ $dropdown_content.prepend(self.render('option_create', {input: query}));
2411
+ $create = $($dropdown_content[0].childNodes[0]);
2412
+ }
2413
+ }
2414
+
2415
+ // activate
2416
+ self.hasOptions = results.items.length > 0 || ( has_create_option && self.settings.showAddOptionOnCreate ) || self.settings.setFirstOptionActive;
2417
+ if (self.hasOptions) {
2418
+ if (results.items.length > 0) {
2419
+ $active_before = active_before && self.getOption(active_before);
2420
+ if (results.query !== "" && self.settings.setFirstOptionActive) {
2421
+ $active = $dropdown_content.find('[data-selectable]:first')
2422
+ } else if (results.query !== "" && $active_before && $active_before.length) {
2423
+ $active = $active_before;
2424
+ } else if (self.settings.mode === 'single' && self.items.length) {
2425
+ $active = self.getOption(self.items[0]);
2426
+ }
2427
+ if (!$active || !$active.length) {
2428
+ if ($create && !self.settings.addPrecedence) {
2429
+ $active = self.getAdjacentOption($create, 1);
2430
+ } else {
2431
+ $active = $dropdown_content.find('[data-selectable]:first');
2432
+ }
2433
+ }
2434
+ } else {
2435
+ $active = $create;
2436
+ }
2437
+ self.setActiveOption($active);
2438
+ if (triggerDropdown && !self.isOpen) { self.open(); }
2439
+ } else {
2440
+ self.setActiveOption(null);
2441
+ if (triggerDropdown && self.isOpen) { self.close(); }
2442
+ }
2443
+ },
2444
+
2445
+ /**
2446
+ * Adds an available option. If it already exists,
2447
+ * nothing will happen. Note: this does not refresh
2448
+ * the options list dropdown (use `refreshOptions`
2449
+ * for that).
2450
+ *
2451
+ * Usage:
2452
+ *
2453
+ * this.addOption(data)
2454
+ *
2455
+ * @param {object|array} data
2456
+ */
2457
+ addOption: function(data) {
2458
+ var i, n, value, self = this;
2459
+
2460
+ if (Array.isArray(data)) {
2461
+ for (i = 0, n = data.length; i < n; i++) {
2462
+ self.addOption(data[i]);
2463
+ }
2464
+ return;
2465
+ }
2466
+
2467
+ if (value = self.registerOption(data)) {
2468
+ self.userOptions[value] = true;
2469
+ self.lastQuery = null;
2470
+ self.trigger('option_add', value, data);
2471
+ }
2472
+ },
2473
+
2474
+ /**
2475
+ * Registers an option to the pool of options.
2476
+ *
2477
+ * @param {object} data
2478
+ * @return {boolean|string}
2479
+ */
2480
+ registerOption: function(data) {
2481
+ var key = hash_key(data[this.settings.valueField]);
2482
+ if (typeof key === 'undefined' || key === null || this.options.hasOwnProperty(key)) return false;
2483
+ data.$order = data.$order || ++this.order;
2484
+ this.options[key] = data;
2485
+ return key;
2486
+ },
2487
+
2488
+ /**
2489
+ * Registers an option group to the pool of option groups.
2490
+ *
2491
+ * @param {object} data
2492
+ * @return {boolean|string}
2493
+ */
2494
+ registerOptionGroup: function(data) {
2495
+ var key = hash_key(data[this.settings.optgroupValueField]);
2496
+ if (!key) return false;
2497
+
2498
+ data.$order = data.$order || ++this.order;
2499
+ this.optgroups[key] = data;
2500
+ return key;
2501
+ },
2502
+
2503
+ /**
2504
+ * Registers a new optgroup for options
2505
+ * to be bucketed into.
2506
+ *
2507
+ * @param {string} id
2508
+ * @param {object} data
2509
+ */
2510
+ addOptionGroup: function(id, data) {
2511
+ data[this.settings.optgroupValueField] = id;
2512
+ if (id = this.registerOptionGroup(data)) {
2513
+ this.trigger('optgroup_add', id, data);
2514
+ }
2515
+ },
2516
+
2517
+ /**
2518
+ * Removes an existing option group.
2519
+ *
2520
+ * @param {string} id
2521
+ */
2522
+ removeOptionGroup: function(id) {
2523
+ if (this.optgroups.hasOwnProperty(id)) {
2524
+ delete this.optgroups[id];
2525
+ this.renderCache = {};
2526
+ this.trigger('optgroup_remove', id);
2527
+ }
2528
+ },
2529
+
2530
+ /**
2531
+ * Clears all existing option groups.
2532
+ */
2533
+ clearOptionGroups: function() {
2534
+ this.optgroups = {};
2535
+ this.renderCache = {};
2536
+ this.trigger('optgroup_clear');
2537
+ },
2538
+
2539
+ /**
2540
+ * Updates an option available for selection. If
2541
+ * it is visible in the selected items or options
2542
+ * dropdown, it will be re-rendered automatically.
2543
+ *
2544
+ * @param {string} value
2545
+ * @param {object} data
2546
+ */
2547
+ updateOption: function(value, data) {
2548
+ var self = this;
2549
+ var $item, $item_new;
2550
+ var value_new, index_item, cache_items, cache_options, order_old;
2551
+
2552
+ value = hash_key(value);
2553
+ value_new = hash_key(data[self.settings.valueField]);
2554
+
2555
+ // sanity checks
2556
+ if (value === null) return;
2557
+ if (!self.options.hasOwnProperty(value)) return;
2558
+ if (typeof value_new !== 'string') throw new Error('Value must be set in option data');
2559
+
2560
+ order_old = self.options[value].$order;
2561
+
2562
+ // update references
2563
+ if (value_new !== value) {
2564
+ delete self.options[value];
2565
+ index_item = self.items.indexOf(value);
2566
+ if (index_item !== -1) {
2567
+ self.items.splice(index_item, 1, value_new);
2568
+ }
2569
+ }
2570
+ data.$order = data.$order || order_old;
2571
+ self.options[value_new] = data;
2572
+
2573
+ // invalidate render cache
2574
+ cache_items = self.renderCache['item'];
2575
+ cache_options = self.renderCache['option'];
2576
+
2577
+ if (cache_items) {
2578
+ delete cache_items[value];
2579
+ delete cache_items[value_new];
2580
+ }
2581
+ if (cache_options) {
2582
+ delete cache_options[value];
2583
+ delete cache_options[value_new];
2584
+ }
2585
+
2586
+ // update the item if it's selected
2587
+ if (self.items.indexOf(value_new) !== -1) {
2588
+ $item = self.getItem(value);
2589
+ $item_new = $(self.render('item', data));
2590
+ if ($item.hasClass('active')) $item_new.addClass('active');
2591
+ $item.replaceWith($item_new);
2592
+ }
2593
+
2594
+ // invalidate last query because we might have updated the sortField
2595
+ self.lastQuery = null;
2596
+
2597
+ // update dropdown contents
2598
+ if (self.isOpen) {
2599
+ self.refreshOptions(false);
2600
+ }
2601
+ },
2602
+
2603
+ /**
2604
+ * Removes a single option.
2605
+ *
2606
+ * @param {string} value
2607
+ * @param {boolean} silent
2608
+ */
2609
+ removeOption: function(value, silent) {
2610
+ var self = this;
2611
+ value = hash_key(value);
2612
+
2613
+ var cache_items = self.renderCache['item'];
2614
+ var cache_options = self.renderCache['option'];
2615
+ if (cache_items) delete cache_items[value];
2616
+ if (cache_options) delete cache_options[value];
2617
+
2618
+ delete self.userOptions[value];
2619
+ delete self.options[value];
2620
+ self.lastQuery = null;
2621
+ self.trigger('option_remove', value);
2622
+ self.removeItem(value, silent);
2623
+ },
2624
+
2625
+ /**
2626
+ * Clears all options, including all selected items
2627
+ *
2628
+ * @param {boolean} silent
2629
+ */
2630
+ clearOptions: function(silent) {
2631
+ var self = this;
2632
+
2633
+ self.loadedSearches = {};
2634
+ self.userOptions = {};
2635
+ self.renderCache = {};
2636
+ var options = self.options;
2637
+ $.each(self.options, function(key, value) {
2638
+ if(self.items.indexOf(key) == -1) {
2639
+ delete options[key];
2640
+ }
2641
+ });
2642
+ self.options = self.sifter.items = options;
2643
+ self.lastQuery = null;
2644
+ self.trigger('option_clear');
2645
+ self.clear(silent);
2646
+ },
2647
+
2648
+ /**
2649
+ * Returns the jQuery element of the option
2650
+ * matching the given value.
2651
+ *
2652
+ * @param {string} value
2653
+ * @returns {object}
2654
+ */
2655
+ getOption: function(value) {
2656
+ return this.getElementWithValue(value, this.$dropdown_content.find('[data-selectable]'));
2657
+ },
2658
+
2659
+ /**
2660
+ * Returns the jQuery element of the first
2661
+ * selectable option.
2662
+ *
2663
+ * @return {object}
2664
+ */
2665
+ getFirstOption: function() {
2666
+ var $options = this.$dropdown.find('[data-selectable]');
2667
+ return $options.length > 0 ? $options.eq(0) : $();
2668
+ },
2669
+
2670
+ /**
2671
+ * Returns the jQuery element of the next or
2672
+ * previous selectable option.
2673
+ *
2674
+ * @param {object} $option
2675
+ * @param {int} direction can be 1 for next or -1 for previous
2676
+ * @return {object}
2677
+ */
2678
+ getAdjacentOption: function($option, direction) {
2679
+ var $options = this.$dropdown.find('[data-selectable]');
2680
+ var index = $options.index($option) + direction;
2681
+
2682
+ return index >= 0 && index < $options.length ? $options.eq(index) : $();
2683
+ },
2684
+
2685
+ /**
2686
+ * Finds the first element with a "data-value" attribute
2687
+ * that matches the given value.
2688
+ *
2689
+ * @param {mixed} value
2690
+ * @param {object} $els
2691
+ * @return {object}
2692
+ */
2693
+ getElementWithValue: function(value, $els) {
2694
+ value = hash_key(value);
2695
+
2696
+ if (typeof value !== 'undefined' && value !== null) {
2697
+ for (var i = 0, n = $els.length; i < n; i++) {
2698
+ if ($els[i].getAttribute('data-value') === value) {
2699
+ return $($els[i]);
2700
+ }
2701
+ }
2702
+ }
2703
+
2704
+ return $();
2705
+ },
2706
+
2707
+ /**
2708
+ * Finds the first element with a "textContent" property
2709
+ * that matches the given textContent value.
2710
+ *
2711
+ * @param {mixed} textContent
2712
+ * @param {boolean} ignoreCase
2713
+ * @param {object} $els
2714
+ * @return {object}
2715
+ */
2716
+ getElementWithTextContent: function(textContent, ignoreCase ,$els) {
2717
+ textContent = hash_key(textContent);
2718
+
2719
+ if (typeof textContent !== 'undefined' && textContent !== null) {
2720
+ for (var i = 0, n = $els.length; i < n; i++) {
2721
+ var eleTextContent = $els[i].textContent
2722
+ if (ignoreCase == true) {
2723
+ eleTextContent = (eleTextContent !== null) ? eleTextContent.toLowerCase() : null;
2724
+ textContent = textContent.toLowerCase();
2725
+ }
2726
+ if (eleTextContent === textContent) {
2727
+ return $($els[i]);
2728
+ }
2729
+ }
2730
+ }
2731
+
2732
+ return $();
2733
+ },
2734
+
2735
+ /**
2736
+ * Returns the jQuery element of the item
2737
+ * matching the given value.
2738
+ *
2739
+ * @param {string} value
2740
+ * @returns {object}
2741
+ */
2742
+ getItem: function(value) {
2743
+ return this.getElementWithValue(value, this.$control.children());
2744
+ },
2745
+
2746
+ /**
2747
+ * Returns the jQuery element of the item
2748
+ * matching the given textContent.
2749
+ *
2750
+ * @param {string} value
2751
+ * @param {boolean} ignoreCase
2752
+ * @returns {object}
2753
+ */
2754
+ getFirstItemMatchedByTextContent: function(textContent, ignoreCase) {
2755
+ ignoreCase = (ignoreCase !== null && ignoreCase === true) ? true : false;
2756
+ return this.getElementWithTextContent(textContent, ignoreCase, this.$dropdown_content.find('[data-selectable]'));
2757
+ },
2758
+
2759
+ /**
2760
+ * "Selects" multiple items at once. Adds them to the list
2761
+ * at the current caret position.
2762
+ *
2763
+ * @param {string} values
2764
+ * @param {boolean} silent
2765
+ */
2766
+ addItems: function(values, silent) {
2767
+ this.buffer = document.createDocumentFragment();
2768
+
2769
+ var childNodes = this.$control[0].childNodes;
2770
+ for (var i = 0; i < childNodes.length; i++) {
2771
+ this.buffer.appendChild(childNodes[i]);
2772
+ }
2773
+
2774
+ var items = Array.isArray(values) ? values : [values];
2775
+ for (var i = 0, n = items.length; i < n; i++) {
2776
+ this.isPending = (i < n - 1);
2777
+ this.addItem(items[i], silent);
2778
+ }
2779
+
2780
+ var control = this.$control[0];
2781
+ control.insertBefore(this.buffer, control.firstChild);
2782
+
2783
+ this.buffer = null;
2784
+ },
2785
+
2786
+ /**
2787
+ * "Selects" an item. Adds it to the list
2788
+ * at the current caret position.
2789
+ *
2790
+ * @param {string} value
2791
+ * @param {boolean} silent
2792
+ */
2793
+ addItem: function(value, silent) {
2794
+ var events = silent ? [] : ['change'];
2795
+
2796
+ debounce_events(this, events, function() {
2797
+ var $item, $option, $options;
2798
+ var self = this;
2799
+ var inputMode = self.settings.mode;
2800
+ var i, active, value_next, wasFull;
2801
+ value = hash_key(value);
2802
+
2803
+ if (self.items.indexOf(value) !== -1) {
2804
+ if (inputMode === 'single') self.close();
2805
+ return;
2806
+ }
2807
+
2808
+ if (!self.options.hasOwnProperty(value)) return;
2809
+ if (inputMode === 'single') self.clear(silent);
2810
+ if (inputMode === 'multi' && self.isFull()) return;
2811
+
2812
+ $item = $(self.render('item', self.options[value]));
2813
+ wasFull = self.isFull();
2814
+ self.items.splice(self.caretPos, 0, value);
2815
+ self.insertAtCaret($item);
2816
+ if (!self.isPending || (!wasFull && self.isFull())) {
2817
+ self.refreshState();
2818
+ }
2819
+
2820
+ if (self.isSetup) {
2821
+ $options = self.$dropdown_content.find('[data-selectable]');
2822
+
2823
+ // update menu / remove the option (if this is not one item being added as part of series)
2824
+ if (!self.isPending) {
2825
+ $option = self.getOption(value);
2826
+ value_next = self.getAdjacentOption($option, 1).attr('data-value');
2827
+ self.refreshOptions(self.isFocused && inputMode !== 'single');
2828
+ if (value_next) {
2829
+ self.setActiveOption(self.getOption(value_next));
2830
+ }
2831
+ }
2832
+
2833
+ // hide the menu if the maximum number of items have been selected or no options are left
2834
+ if (!$options.length || self.isFull()) {
2835
+ self.close();
2836
+ } else if (!self.isPending) {
2837
+ self.positionDropdown();
2838
+ }
2839
+
2840
+ self.updatePlaceholder();
2841
+ self.trigger('item_add', value, $item);
2842
+
2843
+ if (!self.isPending) {
2844
+ self.updateOriginalInput({silent: silent});
2845
+ }
2846
+ }
2847
+ });
2848
+ },
2849
+
2850
+ /**
2851
+ * Removes the selected item matching
2852
+ * the provided value.
2853
+ *
2854
+ * @param {string} value
2855
+ */
2856
+ removeItem: function(value, silent) {
2857
+ var self = this;
2858
+ var $item, i, idx;
2859
+
2860
+ $item = (value instanceof $) ? value : self.getItem(value);
2861
+ value = hash_key($item.attr('data-value'));
2862
+ i = self.items.indexOf(value);
2863
+
2864
+ if (i !== -1) {
2865
+ self.trigger('item_before_remove', value, $item);
2866
+ $item.remove();
2867
+ if ($item.hasClass('active')) {
2868
+ $item.removeClass('active');
2869
+ idx = self.$activeItems.indexOf($item[0]);
2870
+ self.$activeItems.splice(idx, 1);
2871
+ $item.removeClass('active');
2872
+ }
2873
+
2874
+ self.items.splice(i, 1);
2875
+ self.lastQuery = null;
2876
+ if (!self.settings.persist && self.userOptions.hasOwnProperty(value)) {
2877
+ self.removeOption(value, silent);
2878
+ }
2879
+
2880
+ if (i < self.caretPos) {
2881
+ self.setCaret(self.caretPos - 1);
2882
+ }
2883
+
2884
+ self.refreshState();
2885
+ self.updatePlaceholder();
2886
+ self.updateOriginalInput({silent: silent});
2887
+ self.positionDropdown();
2888
+ self.trigger('item_remove', value, $item);
2889
+ }
2890
+ },
2891
+
2892
+ /**
2893
+ * Invokes the `create` method provided in the
2894
+ * selectize options that should provide the data
2895
+ * for the new item, given the user input.
2896
+ *
2897
+ * Once this completes, it will be added
2898
+ * to the item list.
2899
+ *
2900
+ * @param {string} value
2901
+ * @param {boolean} [triggerDropdown]
2902
+ * @param {function} [callback]
2903
+ * @return {boolean}
2904
+ */
2905
+ createItem: function(input, triggerDropdown) {
2906
+ var self = this;
2907
+ var caret = self.caretPos;
2908
+ input = input || (self.$control_input.val() || '').trim();
2909
+
2910
+ var callback = arguments[arguments.length - 1];
2911
+ if (typeof callback !== 'function') callback = function() {};
2912
+
2913
+ if (typeof triggerDropdown !== 'boolean') {
2914
+ triggerDropdown = true;
2915
+ }
2916
+
2917
+ if (!self.canCreate(input)) {
2918
+ callback();
2919
+ return false;
2920
+ }
2921
+
2922
+ self.lock();
2923
+
2924
+ var setup = (typeof self.settings.create === 'function') ? this.settings.create : function(input) {
2925
+ var data = {};
2926
+ data[self.settings.labelField] = input;
2927
+ var key = input;
2928
+ if ( self.settings.formatValueToKey && typeof self.settings.formatValueToKey === 'function' ) {
2929
+ key = self.settings.formatValueToKey.apply(this, [key]);
2930
+ if (key === null || typeof key === 'undefined' || typeof key === 'object' || typeof key === 'function') {
2931
+ throw new Error('Selectize "formatValueToKey" setting must be a function that returns a value other than object or function.');
2932
+ }
2933
+ }
2934
+ data[self.settings.valueField] = key;
2935
+ return data;
2936
+ };
2937
+
2938
+ var create = once(function(data) {
2939
+ self.unlock();
2940
+
2941
+ if (!data || typeof data !== 'object') return callback();
2942
+ var value = hash_key(data[self.settings.valueField]);
2943
+ if (typeof value !== 'string') return callback();
2944
+
2945
+ self.setTextboxValue('');
2946
+ self.addOption(data);
2947
+ self.setCaret(caret);
2948
+ self.addItem(value);
2949
+ self.refreshOptions(triggerDropdown && self.settings.mode !== 'single');
2950
+ callback(data);
2951
+ });
2952
+
2953
+ var output = setup.apply(this, [input, create]);
2954
+ if (typeof output !== 'undefined') {
2955
+ create(output);
2956
+ }
2957
+
2958
+ return true;
2959
+ },
2960
+
2961
+ /**
2962
+ * Re-renders the selected item lists.
2963
+ */
2964
+ refreshItems: function(silent) {
2965
+ this.lastQuery = null;
2966
+
2967
+ if (this.isSetup) {
2968
+ this.addItem(this.items, silent);
2969
+ }
2970
+
2971
+ this.refreshState();
2972
+ this.updateOriginalInput({silent: silent});
2973
+ },
2974
+
2975
+ /**
2976
+ * Updates all state-dependent attributes
2977
+ * and CSS classes.
2978
+ */
2979
+ refreshState: function() {
2980
+ this.refreshValidityState();
2981
+ this.refreshClasses();
2982
+ },
2983
+
2984
+ /**
2985
+ * Update the `required` attribute of both input and control input.
2986
+ *
2987
+ * The `required` property needs to be activated on the control input
2988
+ * for the error to be displayed at the right place. `required` also
2989
+ * needs to be temporarily deactivated on the input since the input is
2990
+ * hidden and can't show errors.
2991
+ */
2992
+ refreshValidityState: function() {
2993
+ if (!this.isRequired) return false;
2994
+
2995
+ var invalid = !this.items.length;
2996
+
2997
+ this.isInvalid = invalid;
2998
+ this.$control_input.prop('required', invalid);
2999
+ this.$input.prop('required', !invalid);
3000
+ },
3001
+
3002
+ /**
3003
+ * Updates all state-dependent CSS classes.
3004
+ */
3005
+ refreshClasses: function() {
3006
+ var self = this;
3007
+ var isFull = self.isFull();
3008
+ var isLocked = self.isLocked;
3009
+
3010
+ self.$wrapper
3011
+ .toggleClass('rtl', self.rtl);
3012
+
3013
+ self.$control
3014
+ .toggleClass('focus', self.isFocused)
3015
+ .toggleClass('disabled', self.isDisabled)
3016
+ .toggleClass('required', self.isRequired)
3017
+ .toggleClass('invalid', self.isInvalid)
3018
+ .toggleClass('locked', isLocked)
3019
+ .toggleClass('full', isFull).toggleClass('not-full', !isFull)
3020
+ .toggleClass('input-active', self.isFocused && !self.isInputHidden)
3021
+ .toggleClass('dropdown-active', self.isOpen)
3022
+ .toggleClass('has-options', !$.isEmptyObject(self.options))
3023
+ .toggleClass('has-items', self.items.length > 0);
3024
+
3025
+ self.$control_input.data('grow', !isFull && !isLocked);
3026
+ },
3027
+
3028
+ /**
3029
+ * Determines whether or not more items can be added
3030
+ * to the control without exceeding the user-defined maximum.
3031
+ *
3032
+ * @returns {boolean}
3033
+ */
3034
+ isFull: function() {
3035
+ return this.settings.maxItems !== null && this.items.length >= this.settings.maxItems;
3036
+ },
3037
+
3038
+ /**
3039
+ * Refreshes the original <select> or <input>
3040
+ * element to reflect the current state.
3041
+ */
3042
+ updateOriginalInput: function(opts) {
3043
+ var i, n, existing, fresh, old, $options, label, value, values, self = this;
3044
+ opts = opts || {};
3045
+
3046
+ if (self.tagType === TAG_SELECT) {
3047
+ $options = self.$input.find('option');
3048
+ existing = [];
3049
+ fresh = [];
3050
+ old = [];
3051
+ values = [];
3052
+
3053
+ $options.get().forEach(function(option) {
3054
+ existing.push(option.value);
3055
+ });
3056
+
3057
+ self.items.forEach(function(item) {
3058
+ label = self.options[item][self.settings.labelField] || '';
3059
+
3060
+ values.push(item);
3061
+
3062
+ if (existing.indexOf(item) != -1) {
3063
+ return;
3064
+ }
3065
+
3066
+ fresh.push('<option value="' + escape_html(item) + '" selected="selected">' + escape_html(label) + '</option>');
3067
+ });
3068
+
3069
+ old = existing.filter(function(value) {
3070
+ return values.indexOf(value) < 0;
3071
+ }).map(function(value) {
3072
+ return 'option[value="' + value + '"]';
3073
+ });
3074
+
3075
+ if (existing.length - old.length + fresh.length === 0 && !self.$input.attr('multiple')) {
3076
+ fresh.push('<option value="" selected="selected"></option>');
3077
+ }
3078
+
3079
+ self.$input.find(old.join(', ')).remove();
3080
+ self.$input.append(fresh.join(''));
3081
+ } else {
3082
+ self.$input.val(self.getValue());
3083
+ self.$input.attr('value',self.$input.val());
3084
+ }
3085
+
3086
+ if (self.isSetup) {
3087
+ if (!opts.silent) {
3088
+ self.trigger('change', self.$input.val());
3089
+ }
3090
+ }
3091
+ },
3092
+
3093
+ /**
3094
+ * Shows/hide the input placeholder depending
3095
+ * on if there items in the list already.
3096
+ */
3097
+ updatePlaceholder: function() {
3098
+ if (!this.settings.placeholder) return;
3099
+ var $input = this.$control_input;
3100
+
3101
+ if (this.items.length) {
3102
+ $input.removeAttr('placeholder');
3103
+ } else {
3104
+ $input.attr('placeholder', this.settings.placeholder);
3105
+ }
3106
+ $input.triggerHandler('update', {force: true});
3107
+ },
3108
+
3109
+ /**
3110
+ * Shows the autocomplete dropdown containing
3111
+ * the available options.
3112
+ */
3113
+ open: function() {
3114
+ var self = this;
3115
+
3116
+ if (
3117
+ self.isLocked ||
3118
+ self.isOpen ||
3119
+ (self.settings.mode === "multi" && self.isFull())
3120
+ )
3121
+ return;
3122
+ self.focus();
3123
+ self.isOpen = true;
3124
+ self.refreshState();
3125
+ self.$dropdown.css({ visibility: 'hidden', display: 'block' });
3126
+ self.setupDropdownHeight();
3127
+ self.positionDropdown();
3128
+ self.$dropdown.css({visibility: 'visible'});
3129
+ self.trigger('dropdown_open', self.$dropdown);
3130
+ },
3131
+
3132
+ /**
3133
+ * Closes the autocomplete dropdown menu.
3134
+ */
3135
+ close: function() {
3136
+ var self = this;
3137
+ var trigger = self.isOpen;
3138
+
3139
+ if (self.settings.mode === 'single' && self.items.length) {
3140
+ self.hideInput();
3141
+
3142
+ // Do not trigger blur while inside a blur event,
3143
+ // this fixes some weird tabbing behavior in FF and IE.
3144
+ // See #1164
3145
+ if (self.isBlurring) {
3146
+ self.$control_input[0].blur(); // close keyboard on iOS
3147
+ }
3148
+ }
3149
+
3150
+ self.isOpen = false;
3151
+ self.$dropdown.hide();
3152
+ self.setActiveOption(null);
3153
+ self.refreshState();
3154
+
3155
+ if (trigger) self.trigger('dropdown_close', self.$dropdown);
3156
+ },
3157
+
3158
+ /**
3159
+ * Calculates and applies the appropriate
3160
+ * position of the dropdown.
3161
+ */
3162
+ positionDropdown: function() {
3163
+ var $control = this.$control;
3164
+ var offset = this.settings.dropdownParent === 'body' ? $control.offset() : $control.position();
3165
+ offset.top += $control.outerHeight(true);
3166
+ var w = $control[0].getBoundingClientRect().width;
3167
+ if (this.settings.minWidth && this.settings.minWidth > w)
3168
+ {
3169
+ w = this.settings.minWidth;
3170
+ }
3171
+ this.$dropdown.css({
3172
+ width : w,
3173
+ top : offset.top,
3174
+ left : offset.left
3175
+ });
3176
+ },
3177
+
3178
+ setupDropdownHeight: function () {
3179
+ if (typeof this.settings.dropdownSize === 'object' && this.settings.dropdownSize.sizeType !== 'auto') {
3180
+ var height = this.settings.dropdownSize.sizeValue;
3181
+
3182
+ if (this.settings.dropdownSize.sizeType === 'numberItems') {
3183
+ // retrieve all items (included optgroup but exept the container .optgroup)
3184
+ var $items = this.$dropdown_content.find('*').not('.optgroup, .highlight').not(this.settings.ignoreOnDropwdownHeight);
3185
+ var totalHeight = 0;
3186
+ var marginTop = 0;
3187
+ var marginBottom = 0;
3188
+ var separatorHeight = 0;
3189
+
3190
+
3191
+ for (var i = 0; i < height; i++) {
3192
+ var $item = $($items[i]);
3193
+
3194
+ if ($item.length === 0) {
3195
+ break;
3196
+ }
3197
+
3198
+ totalHeight += $item.outerHeight(true);
3199
+ // If not selectable, it's an optgroup so we "ignore" for count items
3200
+ if (typeof $item.data('selectable') == 'undefined') {
3201
+ if ($item.hasClass('optgroup-header')) {
3202
+ var styles = window.getComputedStyle($item.parent()[0], ':before');
3203
+
3204
+ if (styles) {
3205
+ marginTop = styles.marginTop ? Number(styles.marginTop.replace(/\W*(\w)\w*/g, '$1')) : 0;
3206
+ marginBottom = styles.marginBottom ? Number(styles.marginBottom.replace(/\W*(\w)\w*/g, '$1')) : 0;
3207
+ separatorHeight = styles.borderTopWidth ? Number(styles.borderTopWidth.replace(/\W*(\w)\w*/g, '$1')) : 0;
3208
+ }
3209
+ }
3210
+ height++;
3211
+ }
3212
+
3213
+ }
3214
+
3215
+ // Get padding top for add to global height
3216
+ var paddingTop = this.$dropdown_content.css('padding-top') ? Number(this.$dropdown_content.css('padding-top').replace(/\W*(\w)\w*/g, '$1')) : 0;
3217
+ var paddingBottom = this.$dropdown_content.css('padding-bottom') ? Number(this.$dropdown_content.css('padding-bottom').replace(/\W*(\w)\w*/g, '$1')) : 0;
3218
+
3219
+ height = (totalHeight + paddingTop + paddingBottom + marginTop + marginBottom + separatorHeight) + 'px';
3220
+ } else if (this.settings.dropdownSize.sizeType !== 'fixedHeight') {
3221
+ console.warn('Selectize.js - Value of "sizeType" must be "fixedHeight" or "numberItems');
3222
+ return;
3223
+ }
3224
+
3225
+ this.$dropdown_content.css({ height: height, maxHeight: 'none' });
3226
+ }
3227
+ },
3228
+
3229
+ /**
3230
+ * Resets / clears all selected items
3231
+ * from the control.
3232
+ *
3233
+ * @param {boolean} silent
3234
+ */
3235
+ clear: function(silent) {
3236
+ var self = this;
3237
+
3238
+ if (!self.items.length) return;
3239
+ self.$control.children(':not(input)').remove();
3240
+ self.items = [];
3241
+ self.lastQuery = null;
3242
+ self.setCaret(0);
3243
+ self.setActiveItem(null);
3244
+ self.updatePlaceholder();
3245
+ self.updateOriginalInput({silent: silent});
3246
+ self.refreshState();
3247
+ self.showInput();
3248
+ self.trigger('clear');
3249
+ },
3250
+
3251
+ /**
3252
+ * A helper method for inserting an element
3253
+ * at the current caret position.
3254
+ *
3255
+ * @param {object} $el
3256
+ */
3257
+ insertAtCaret: function($el) {
3258
+ var caret = Math.min(this.caretPos, this.items.length);
3259
+ var el = $el[0];
3260
+ /**
3261
+ * @type {HTMLElement}
3262
+ **/
3263
+ var target = this.buffer || this.$control[0];
3264
+
3265
+ if (caret === 0) {
3266
+ target.insertBefore(el, target.firstChild);
3267
+ } else {
3268
+ target.insertBefore(el, target.childNodes[caret]);
3269
+ }
3270
+
3271
+ this.setCaret(caret + 1);
3272
+ },
3273
+
3274
+ /**
3275
+ * Removes the current selected item(s).
3276
+ *
3277
+ * @param {object} e (optional)
3278
+ * @returns {boolean}
3279
+ */
3280
+ deleteSelection: function(e) {
3281
+ var i, n, direction, selection, values, caret, option_select, $option_select, $tail;
3282
+ var self = this;
3283
+
3284
+ direction = (e && e.keyCode === KEY_BACKSPACE) ? -1 : 1;
3285
+ selection = getInputSelection(self.$control_input[0]);
3286
+
3287
+ if (self.$activeOption && !self.settings.hideSelected) {
3288
+ if (typeof self.settings.deselectBehavior === 'string' && self.settings.deselectBehavior === 'top') {
3289
+ option_select = self.getFirstOption().attr('data-value');
3290
+ } else {
3291
+ option_select = self.getAdjacentOption(self.$activeOption, -1).attr('data-value');
3292
+ }
3293
+ }
3294
+
3295
+ // determine items that will be removed
3296
+ values = [];
3297
+
3298
+ if (self.$activeItems.length) {
3299
+ $tail = self.$control.children('.active:' + (direction > 0 ? 'last' : 'first'));
3300
+ caret = self.$control.children(':not(input)').index($tail);
3301
+ if (direction > 0) { caret++; }
3302
+
3303
+ for (i = 0, n = self.$activeItems.length; i < n; i++) {
3304
+ values.push($(self.$activeItems[i]).attr('data-value'));
3305
+ }
3306
+ if (e) {
3307
+ e.preventDefault();
3308
+ e.stopPropagation();
3309
+ }
3310
+ } else if ((self.isFocused || self.settings.mode === 'single') && self.items.length) {
3311
+ if (direction < 0 && selection.start === 0 && selection.length === 0) {
3312
+ values.push(self.items[self.caretPos - 1]);
3313
+ } else if (direction > 0 && selection.start === self.$control_input.val().length) {
3314
+ values.push(self.items[self.caretPos]);
3315
+ }
3316
+ }
3317
+
3318
+ // allow the callback to abort
3319
+ if (!values.length || (typeof self.settings.onDelete === 'function' && self.settings.onDelete.apply(self, [values]) === false)) {
3320
+ return false;
3321
+ }
3322
+
3323
+ // perform removal
3324
+ if (typeof caret !== 'undefined') {
3325
+ self.setCaret(caret);
3326
+ }
3327
+ while (values.length) {
3328
+ self.removeItem(values.pop());
3329
+ }
3330
+
3331
+ self.showInput();
3332
+ self.positionDropdown();
3333
+ self.refreshOptions(true);
3334
+
3335
+ // select previous option
3336
+ if (option_select) {
3337
+ $option_select = self.getOption(option_select);
3338
+ if ($option_select.length) {
3339
+ self.setActiveOption($option_select);
3340
+ }
3341
+ }
3342
+
3343
+ return true;
3344
+ },
3345
+
3346
+ /**
3347
+ * Selects the previous / next item (depending
3348
+ * on the `direction` argument).
3349
+ *
3350
+ * > 0 - right
3351
+ * < 0 - left
3352
+ *
3353
+ * @param {int} direction
3354
+ * @param {object} e (optional)
3355
+ */
3356
+ advanceSelection: function(direction, e) {
3357
+ var tail, selection, idx, valueLength, cursorAtEdge, $tail;
3358
+ var self = this;
3359
+
3360
+ if (direction === 0) return;
3361
+ if (self.rtl) direction *= -1;
3362
+
3363
+ tail = direction > 0 ? 'last' : 'first';
3364
+ selection = getInputSelection(self.$control_input[0]);
3365
+
3366
+ if (self.isFocused && !self.isInputHidden) {
3367
+ valueLength = self.$control_input.val().length;
3368
+ cursorAtEdge = direction < 0
3369
+ ? selection.start === 0 && selection.length === 0
3370
+ : selection.start === valueLength;
3371
+
3372
+ if (cursorAtEdge && !valueLength) {
3373
+ self.advanceCaret(direction, e);
3374
+ }
3375
+ } else {
3376
+ $tail = self.$control.children('.active:' + tail);
3377
+ if ($tail.length) {
3378
+ idx = self.$control.children(':not(input)').index($tail);
3379
+ self.setActiveItem(null);
3380
+ self.setCaret(direction > 0 ? idx + 1 : idx);
3381
+ }
3382
+ }
3383
+ },
3384
+
3385
+ /**
3386
+ * Moves the caret left / right.
3387
+ *
3388
+ * @param {int} direction
3389
+ * @param {object} e (optional)
3390
+ */
3391
+ advanceCaret: function(direction, e) {
3392
+ var self = this, fn, $adj;
3393
+
3394
+ if (direction === 0) return;
3395
+
3396
+ fn = direction > 0 ? 'next' : 'prev';
3397
+ if (self.isShiftDown) {
3398
+ $adj = self.$control_input[fn]();
3399
+ if ($adj.length) {
3400
+ self.hideInput();
3401
+ self.setActiveItem($adj);
3402
+ e && e.preventDefault();
3403
+ }
3404
+ } else {
3405
+ self.setCaret(self.caretPos + direction);
3406
+ }
3407
+ },
3408
+
3409
+ /**
3410
+ * Moves the caret to the specified index.
3411
+ *
3412
+ * @param {int} i
3413
+ */
3414
+ setCaret: function(i) {
3415
+ var self = this;
3416
+
3417
+ if (self.settings.mode === 'single') {
3418
+ i = self.items.length;
3419
+ } else {
3420
+ i = Math.max(0, Math.min(self.items.length, i));
3421
+ }
3422
+
3423
+ if(!self.isPending) {
3424
+ // the input must be moved by leaving it in place and moving the
3425
+ // siblings, due to the fact that focus cannot be restored once lost
3426
+ // on mobile webkit devices
3427
+ var j, n, fn, $children, $child;
3428
+ $children = self.$control.children(':not(input)');
3429
+ for (j = 0, n = $children.length; j < n; j++) {
3430
+ $child = $($children[j]).detach();
3431
+ if (j < i) {
3432
+ self.$control_input.before($child);
3433
+ } else {
3434
+ self.$control.append($child);
3435
+ }
3436
+ }
3437
+ }
3438
+
3439
+ self.caretPos = i;
3440
+ },
3441
+
3442
+ /**
3443
+ * Disables user input on the control. Used while
3444
+ * items are being asynchronously created.
3445
+ */
3446
+ lock: function() {
3447
+ this.close();
3448
+ this.isLocked = true;
3449
+ this.refreshState();
3450
+ },
3451
+
3452
+ /**
3453
+ * Re-enables user input on the control.
3454
+ */
3455
+ unlock: function() {
3456
+ this.isLocked = false;
3457
+ this.refreshState();
3458
+ },
3459
+
3460
+ /**
3461
+ * Disables user input on the control completely.
3462
+ * While disabled, it cannot receive focus.
3463
+ */
3464
+ disable: function() {
3465
+ var self = this;
3466
+ self.$input.prop('disabled', true);
3467
+ self.$control_input.prop('disabled', true).prop('tabindex', -1);
3468
+ self.isDisabled = true;
3469
+ self.lock();
3470
+ },
3471
+
3472
+ /**
3473
+ * Enables the control so that it can respond
3474
+ * to focus and user input.
3475
+ */
3476
+ enable: function() {
3477
+ var self = this;
3478
+ self.$input.prop('disabled', false);
3479
+ self.$control_input.prop('disabled', false).prop('tabindex', self.tabIndex);
3480
+ self.isDisabled = false;
3481
+ self.unlock();
3482
+ },
3483
+
3484
+ /**
3485
+ * Completely destroys the control and
3486
+ * unbinds all event listeners so that it can
3487
+ * be garbage collected.
3488
+ */
3489
+ destroy: function() {
3490
+ var self = this;
3491
+ var eventNS = self.eventNS;
3492
+ var revertSettings = self.revertSettings;
3493
+
3494
+ self.trigger('destroy');
3495
+ self.off();
3496
+ self.$wrapper.remove();
3497
+ self.$dropdown.remove();
3498
+
3499
+ self.$input
3500
+ .html('')
3501
+ .append(revertSettings.$children)
3502
+ .removeAttr('tabindex')
3503
+ .removeClass('selectized')
3504
+ .attr({tabindex: revertSettings.tabindex})
3505
+ .show();
3506
+
3507
+ self.$control_input.removeData('grow');
3508
+ self.$input.removeData('selectize');
3509
+
3510
+ if (--Selectize.count == 0 && Selectize.$testInput) {
3511
+ Selectize.$testInput.remove();
3512
+ Selectize.$testInput = undefined;
3513
+ }
3514
+
3515
+ $(window).off(eventNS);
3516
+ $(document).off(eventNS);
3517
+ $(document.body).off(eventNS);
3518
+
3519
+ delete self.$input[0].selectize;
3520
+ },
3521
+
3522
+ /**
3523
+ * A helper method for rendering "item" and
3524
+ * "option" templates, given the data.
3525
+ *
3526
+ * @param {string} templateName
3527
+ * @param {object} data
3528
+ * @returns {string}
3529
+ */
3530
+ render: function(templateName, data) {
3531
+ var value, id, label;
3532
+ var html = '';
3533
+ var cache = false;
3534
+ var self = this;
3535
+ var regex_tag = /^[\t \r\n]*<([a-z][a-z0-9\-_]*(?:\:[a-z][a-z0-9\-_]*)?)/i;
3536
+
3537
+ if (templateName === 'option' || templateName === 'item') {
3538
+ value = hash_key(data[self.settings.valueField]);
3539
+ cache = !!value;
3540
+ }
3541
+
3542
+ // pull markup from cache if it exists
3543
+ if (cache) {
3544
+ if (!isset(self.renderCache[templateName])) {
3545
+ self.renderCache[templateName] = {};
3546
+ }
3547
+ if (self.renderCache[templateName].hasOwnProperty(value)) {
3548
+ return self.renderCache[templateName][value];
3549
+ }
3550
+ }
3551
+
3552
+ // render markup
3553
+ html = $(self.settings.render[templateName].apply(this, [data, escape_html]));
3554
+
3555
+ // add mandatory attributes
3556
+ if (templateName === 'option' || templateName === 'option_create') {
3557
+ if (!data[self.settings.disabledField]) {
3558
+ html.attr('data-selectable', '');
3559
+ }
3560
+ }
3561
+ else if (templateName === 'optgroup') {
3562
+ id = data[self.settings.optgroupValueField] || '';
3563
+ html.attr('data-group', id);
3564
+ if(data[self.settings.disabledField]) {
3565
+ html.attr('data-disabled', '');
3566
+ }
3567
+ }
3568
+ if (templateName === 'option' || templateName === 'item') {
3569
+ html.attr('data-value', value || '');
3570
+ }
3571
+
3572
+ // update cache
3573
+ if (cache) {
3574
+ self.renderCache[templateName][value] = html[0];
3575
+ }
3576
+
3577
+ return html[0];
3578
+ },
3579
+
3580
+ /**
3581
+ * Clears the render cache for a template. If
3582
+ * no template is given, clears all render
3583
+ * caches.
3584
+ *
3585
+ * @param {string} templateName
3586
+ */
3587
+ clearCache: function(templateName) {
3588
+ var self = this;
3589
+ if (typeof templateName === 'undefined') {
3590
+ self.renderCache = {};
3591
+ } else {
3592
+ delete self.renderCache[templateName];
3593
+ }
3594
+ },
3595
+
3596
+ /**
3597
+ * Determines whether or not to display the
3598
+ * create item prompt, given a user input.
3599
+ *
3600
+ * @param {string} input
3601
+ * @return {boolean}
3602
+ */
3603
+ canCreate: function(input) {
3604
+ var self = this;
3605
+ if (!self.settings.create) return false;
3606
+ var filter = self.settings.createFilter;
3607
+ return input.length
3608
+ && (typeof filter !== 'function' || filter.apply(self, [input]))
3609
+ && (typeof filter !== 'string' || new RegExp(filter).test(input))
3610
+ && (!(filter instanceof RegExp) || filter.test(input));
3611
+ }
3612
+ });
3613
+
3614
+ Selectize.count = 0;
3615
+ Selectize.defaults = {
3616
+ options: [],
3617
+ optgroups: [],
3618
+
3619
+ plugins: [],
3620
+ delimiter: ',',
3621
+ splitOn: null, // regexp or string for splitting up values from a paste command
3622
+ persist: true,
3623
+ diacritics: true,
3624
+ create: false,
3625
+ showAddOptionOnCreate: true,
3626
+ createOnBlur: false,
3627
+ createFilter: null,
3628
+ highlight: true,
3629
+ openOnFocus: true,
3630
+ maxOptions: 1000,
3631
+ maxItems: null,
3632
+ hideSelected: null,
3633
+ addPrecedence: false,
3634
+ selectOnTab: true,
3635
+ preload: false,
3636
+ allowEmptyOption: false,
3637
+ showEmptyOptionInDropdown: false,
3638
+ emptyOptionLabel: '--',
3639
+ setFirstOptionActive: false,
3640
+ closeAfterSelect: false,
3641
+ closeDropdownThreshold: 250, // number of ms to prevent reopening of dropdown after mousedown
3642
+
3643
+ scrollDuration: 60,
3644
+ deselectBehavior: 'previous', //top, previous
3645
+ loadThrottle: 300,
3646
+ loadingClass: 'loading',
3647
+
3648
+ dataAttr: 'data-data',
3649
+ optgroupField: 'optgroup',
3650
+ valueField: 'value',
3651
+ labelField: 'text',
3652
+ disabledField: 'disabled',
3653
+ optgroupLabelField: 'label',
3654
+ optgroupValueField: 'value',
3655
+ lockOptgroupOrder: false,
3656
+
3657
+ sortField: '$order',
3658
+ searchField: ['text'],
3659
+ searchConjunction: 'and',
3660
+ respect_word_boundaries: true,
3661
+
3662
+ mode: null,
3663
+ wrapperClass: '',
3664
+ inputClass: '',
3665
+ dropdownClass: '',
3666
+ dropdownContentClass: '',
3667
+
3668
+ dropdownParent: null,
3669
+
3670
+ copyClassesToDropdown: true,
3671
+ dropdownSize: {
3672
+ sizeType: 'auto', // 'numberItems' or 'fixedHeight'
3673
+ sizeValue: 'auto', // number of items or height value (px is default) or CSS height (px, rem, em, vh)
3674
+ },
3675
+ normalize: false,
3676
+ ignoreOnDropwdownHeight: 'img, i',
3677
+ search: true,
3678
+
3679
+ /*
3680
+ load : null, // function(query, callback) { ... }
3681
+ score : null, // function(search) { ... }
3682
+ formatValueToKey : null, // function(key) { ... }
3683
+ optionGroupRegister : null, // function(optgroup) to register dynamically created option groups
3684
+ onInitialize : null, // function() { ... }
3685
+ onChange : null, // function(value) { ... }
3686
+ onItemAdd : null, // function(value, $item) { ... }
3687
+ onItemRemove : null, // function(value, $item) { ... }
3688
+ onClear : null, // function() { ... }
3689
+ onOptionAdd : null, // function(value, data) { ... }
3690
+ onOptionRemove : null, // function(value) { ... }
3691
+ onOptionClear : null, // function() { ... }
3692
+ onOptionGroupAdd : null, // function(id, data) { ... }
3693
+ onOptionGroupRemove : null, // function(id) { ... }
3694
+ onOptionGroupClear : null, // function() { ... }
3695
+ onDropdownOpen : null, // function($dropdown) { ... }
3696
+ onDropdownClose : null, // function($dropdown) { ... }
3697
+ onType : null, // function(str) { ... }
3698
+ onDelete : null, // function(values) { ... }
3699
+ */
3700
+
3701
+ render: {
3702
+ /*
3703
+ item: null,
3704
+ optgroup: null,
3705
+ optgroup_header: null,
3706
+ option: null,
3707
+ option_create: null
3708
+ */
3709
+ }
3710
+ };
3711
+
3712
+ $.fn.selectize = function (settings_user) {
3713
+ var defaults = $.fn.selectize.defaults;
3714
+ var settings = $.extend({}, defaults, settings_user);
3715
+ var attr_data = settings.dataAttr;
3716
+ var field_label = settings.labelField;
3717
+ var field_value = settings.valueField;
3718
+ var field_disabled = settings.disabledField;
3719
+ var field_optgroup = settings.optgroupField;
3720
+ var field_optgroup_label = settings.optgroupLabelField;
3721
+ var field_optgroup_value = settings.optgroupValueField;
3722
+
3723
+ /**
3724
+ * Initializes selectize from a <input type="text"> element.
3725
+ *
3726
+ * @param {JQuery} $input
3727
+ * @param {Object} settings_element
3728
+ */
3729
+ var init_textbox = function ($input, settings_element) {
3730
+ var i, n, values, option;
3731
+
3732
+ var data_raw = $input.attr(attr_data);
3733
+
3734
+ if (!data_raw) {
3735
+ var value = ($input.val() || '').trim();
3736
+ if (!settings.allowEmptyOption && !value.length) return;
3737
+ values = value.split(settings.delimiter);
3738
+ for (i = 0, n = values.length; i < n; i++) {
3739
+ option = {};
3740
+ option[field_label] = values[i];
3741
+ option[field_value] = values[i];
3742
+ settings_element.options.push(option);
3743
+ }
3744
+ settings_element.items = values;
3745
+ } else {
3746
+ settings_element.options = JSON.parse(data_raw);
3747
+ for (i = 0, n = settings_element.options.length; i < n; i++) {
3748
+ settings_element.items.push(settings_element.options[i][field_value]);
3749
+ }
3750
+ }
3751
+ };
3752
+
3753
+ /**
3754
+ * Initializes selectize from a <select> element.
3755
+ *
3756
+ * @param {object} $input
3757
+ * @param {object} settings_element
3758
+ */
3759
+ var init_select = function ($input, settings_element) {
3760
+ var i, n, tagName, $children, order = 0;
3761
+ var options = settings_element.options;
3762
+ var optionsMap = {};
3763
+
3764
+ var readData = function ($el) {
3765
+ var data = attr_data && $el.attr(attr_data);
3766
+ var allData = $el.data();
3767
+ var obj = {};
3768
+
3769
+ if (typeof data === 'string' && data.length) {
3770
+ if (isJSON(data)) {
3771
+ Object.assign(obj, JSON.parse(data))
3772
+ } else {
3773
+ obj[data] = data;
3774
+ }
3775
+ }
3776
+
3777
+
3778
+ Object.assign(obj, allData);
3779
+
3780
+ return obj || null;
3781
+ };
3782
+
3783
+ var addOption = function ($option, group) {
3784
+ $option = $($option);
3785
+
3786
+ var value = hash_key($option.val());
3787
+ if (!value && !settings.allowEmptyOption) return;
3788
+
3789
+ // if the option already exists, it's probably been
3790
+ // duplicated in another optgroup. in this case, push
3791
+ // the current group to the "optgroup" property on the
3792
+ // existing option so that it's rendered in both places.
3793
+ if (optionsMap.hasOwnProperty(value)) {
3794
+ if (group) {
3795
+ var arr = optionsMap[value][field_optgroup];
3796
+ if (!arr) {
3797
+ optionsMap[value][field_optgroup] = group;
3798
+ } else if (!Array.isArray(arr)) {
3799
+ optionsMap[value][field_optgroup] = [arr, group];
3800
+ } else {
3801
+ arr.push(group);
3802
+ }
3803
+ }
3804
+ return;
3805
+ }
3806
+
3807
+ var option = readData($option) || {};
3808
+ option[field_label] = option[field_label] || $option.text();
3809
+ option[field_value] = option[field_value] || value;
3810
+ option[field_disabled] = option[field_disabled] || $option.prop('disabled');
3811
+ option[field_optgroup] = option[field_optgroup] || group;
3812
+ option.styles = $option.attr('style') || '';
3813
+ option.classes = $option.attr('class') || '';
3814
+
3815
+ optionsMap[value] = option;
3816
+ options.push(option);
3817
+
3818
+ if ($option.is(':selected')) {
3819
+ settings_element.items.push(value);
3820
+ }
3821
+ };
3822
+
3823
+ var addGroup = function ($optgroup) {
3824
+ var i, n, id, optgroup, $options;
3825
+
3826
+ $optgroup = $($optgroup);
3827
+ id = $optgroup.attr('label');
3828
+
3829
+ if (id) {
3830
+ optgroup = readData($optgroup) || {};
3831
+ optgroup[field_optgroup_label] = id;
3832
+ optgroup[field_optgroup_value] = id;
3833
+ optgroup[field_disabled] = $optgroup.prop('disabled');
3834
+ settings_element.optgroups.push(optgroup);
3835
+ }
3836
+
3837
+ $options = $('option', $optgroup);
3838
+ for (i = 0, n = $options.length; i < n; i++) {
3839
+ addOption($options[i], id);
3840
+ }
3841
+ };
3842
+
3843
+ settings_element.maxItems = $input.attr('multiple') ? null : 1;
3844
+
3845
+ $children = $input.children();
3846
+ for (i = 0, n = $children.length; i < n; i++) {
3847
+ tagName = $children[i].tagName.toLowerCase();
3848
+ if (tagName === 'optgroup') {
3849
+ addGroup($children[i]);
3850
+ } else if (tagName === 'option') {
3851
+ addOption($children[i]);
3852
+ }
3853
+ }
3854
+ };
3855
+
3856
+ return this.each(function () {
3857
+ if (this.selectize) return;
3858
+
3859
+ var instance;
3860
+ var $input = $(this);
3861
+ var tag_name = this.tagName.toLowerCase();
3862
+ var placeholder = $input.attr('placeholder') || $input.attr('data-placeholder');
3863
+ if (!placeholder && !settings.allowEmptyOption) {
3864
+ placeholder = $input.children('option[value=""]').text();
3865
+ }
3866
+ if (settings.allowEmptyOption && settings.showEmptyOptionInDropdown && !$input.children('option[value=""]').length) {
3867
+ var input_html = $input.html();
3868
+ var label = escape_html(settings.emptyOptionLabel || '--');
3869
+ $input.html('<option value="">' + label + '</option>' + input_html);
3870
+ }
3871
+
3872
+ var settings_element = {
3873
+ 'placeholder': placeholder,
3874
+ 'options': [],
3875
+ 'optgroups': [],
3876
+ 'items': []
3877
+ };
3878
+
3879
+ if (tag_name === 'select') {
3880
+ init_select($input, settings_element);
3881
+ } else {
3882
+ init_textbox($input, settings_element);
3883
+ }
3884
+
3885
+ instance = new Selectize($input, $.extend(true, {}, defaults, settings_element, settings_user));
3886
+ instance.settings_user = settings_user;
3887
+ });
3888
+ };
3889
+
3890
+ $.fn.selectize.defaults = Selectize.defaults;
3891
+ $.fn.selectize.support = {
3892
+ validity: SUPPORTS_VALIDITY_API
3893
+ };
3894
+
3895
+ Selectize.define("auto_position", function () {
3896
+ var self = this;
3897
+
3898
+ const POSITION = {
3899
+ top: 'top',
3900
+ bottom: 'bottom',
3901
+ };
3902
+
3903
+ self.positionDropdown = (function() {
3904
+ return function() {
3905
+ const $control = this.$control;
3906
+ const offset = this.settings.dropdownParent === 'body' ? $control.offset() : $control.position();
3907
+ offset.top += $control.outerHeight(true);
3908
+
3909
+ const dropdownHeight = this.$dropdown.prop('scrollHeight') + 5; // 5 - padding value;
3910
+ const controlPosTop = this.$control.get(0).getBoundingClientRect().top;
3911
+ const wrapperHeight = this.$wrapper.height();
3912
+ const position = controlPosTop + dropdownHeight + wrapperHeight > window.innerHeight ? POSITION.top : POSITION.bottom;
3913
+ const styles = {
3914
+ width: $control.outerWidth(),
3915
+ left: offset.left
3916
+ };
3917
+
3918
+ if (position === POSITION.top) {
3919
+ const styleToAdd = { bottom: offset.top, top: 'unset' };
3920
+
3921
+ if (this.settings.dropdownParent === 'body') {
3922
+ styleToAdd.top = offset.top - this.$dropdown.outerHeight(true) - $control.outerHeight(true);
3923
+ styleToAdd.bottom = 'unset';
3924
+ }
3925
+ Object.assign(styles, styleToAdd);
3926
+ this.$dropdown.addClass('selectize-position-top');
3927
+ this.$control.addClass('selectize-position-top');
3928
+ } else {
3929
+ Object.assign(styles, { top: offset.top, bottom: 'unset' });
3930
+ this.$dropdown.removeClass('selectize-position-top');
3931
+ this.$control.removeClass('selectize-position-top');
3932
+ }
3933
+
3934
+ this.$dropdown.css(styles);
3935
+ };
3936
+ }());
3937
+ });
3938
+
3939
+ Selectize.define('auto_select_on_type', function(options) {
3940
+ var self = this;
3941
+
3942
+ self.onBlur = (function() {
3943
+ var originalBlur = self.onBlur;
3944
+ return function(e) {
3945
+ var $matchedItem = self.getFirstItemMatchedByTextContent(self.lastValue, true);
3946
+ if (typeof $matchedItem.attr('data-value') !== 'undefined' && self.getValue() !== $matchedItem.attr('data-value'))
3947
+ {
3948
+ self.setValue($matchedItem.attr('data-value'));
3949
+ }
3950
+ return originalBlur.apply(this, arguments);
3951
+ }
3952
+ }());
3953
+ });
3954
+
3955
+ /**
3956
+ * Plugin: "autofill_disable" (selectize.js)
3957
+ * Copyright (c) 2013 Brian Reavis & contributors
3958
+ * Copyright (c) 2020-2022 Selectize Team & contributors
3959
+ *
3960
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
3961
+ * file except in compliance with the License. You may obtain a copy of the License at:
3962
+ * http://www.apache.org/licenses/LICENSE-2.0
3963
+ *
3964
+ * Unless required by applicable law or agreed to in writing, software distributed under
3965
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
3966
+ * ANY KIND, either express or implied. See the License for the specific language
3967
+ * governing permissions and limitations under the License.
3968
+ *
3969
+ * @author Ris Adams <selectize@risadams.com>
3970
+ */
3971
+
3972
+ Selectize.define("autofill_disable", function (options) {
3973
+ var self = this;
3974
+
3975
+ self.setup = (function () {
3976
+ var original = self.setup;
3977
+ return function () {
3978
+ original.apply(self, arguments);
3979
+
3980
+ // https://stackoverflow.com/questions/30053167/autocomplete-off-vs-false
3981
+ self.$control_input.attr({ autocomplete: "new-password", autofill: "no" });
3982
+ };
3983
+ })();
3984
+ });
3985
+
3986
+ /**
3987
+ * Plugin: "clear_button" (selectize.js)
3988
+ * Copyright (c) 2013 Brian Reavis & contributors
3989
+ * Copyright (c) 2020-2022 Selectize Team & contributors
3990
+ *
3991
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
3992
+ * file except in compliance with the License. You may obtain a copy of the License at:
3993
+ * http://www.apache.org/licenses/LICENSE-2.0
3994
+ *
3995
+ * Unless required by applicable law or agreed to in writing, software distributed under
3996
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
3997
+ * ANY KIND, either express or implied. See the License for the specific language
3998
+ * governing permissions and limitations under the License.
3999
+ *
4000
+ * @author Fabien Winkler <fabien.winkler@outlook.fr>
4001
+ */
4002
+
4003
+ Selectize.define("clear_button", function (options) {
4004
+ var self = this;
4005
+
4006
+ options = $.extend(
4007
+ {
4008
+ title: "Clear",
4009
+ className: "clear",
4010
+ label: "×",
4011
+ html: function (data) {
4012
+ return (
4013
+ '<a class="' + data.className + '" title="' + data.title + '"> ' + data.label + '</a>'
4014
+ );
4015
+ },
4016
+ },
4017
+ options
4018
+ );
4019
+
4020
+ self.setup = (function () {
4021
+ var original = self.setup;
4022
+ return function () {
4023
+ original.apply(self, arguments);
4024
+ self.$button_clear = $(options.html(options));
4025
+
4026
+ if (self.settings.mode === "single") self.$wrapper.addClass("single");
4027
+
4028
+ self.$wrapper.append(self.$button_clear);
4029
+
4030
+ if (self.getValue() === "" || self.getValue().length === 0) {
4031
+ self.$wrapper.find("." + options.className).css("display", "none");
4032
+ }
4033
+
4034
+ self.on("change", function () {
4035
+ if (self.getValue() === "" || self.getValue().length === 0) {
4036
+ self.$wrapper.find("." + options.className).css("display", "none");
4037
+ } else {
4038
+ self.$wrapper.find("." + options.className).css("display", "");
4039
+ }
4040
+ });
4041
+
4042
+ self.$wrapper.on("click", "." + options.className, function (e) {
4043
+ e.preventDefault();
4044
+ e.stopImmediatePropagation();
4045
+ e.stopPropagation();
4046
+
4047
+ if (self.isLocked) return;
4048
+
4049
+ self.clear();
4050
+ self.$wrapper.find("." + options.className).css("display", "none");
4051
+ });
4052
+ };
4053
+ })();
4054
+ });
4055
+
4056
+ /**
4057
+ * Plugin: "drag_drop" (selectize.js)
4058
+ * Copyright (c) 2013 Brian Reavis & contributors
4059
+ * Copyright (c) 2020-2022 Selectize Team & contributors*
4060
+ *
4061
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
4062
+ * file except in compliance with the License. You may obtain a copy of the License at:
4063
+ * http://www.apache.org/licenses/LICENSE-2.0
4064
+ *
4065
+ * Unless required by applicable law or agreed to in writing, software distributed under
4066
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
4067
+ * ANY KIND, either express or implied. See the License for the specific language
4068
+ * governing permissions and limitations under the License.
4069
+ *
4070
+ * @author Brian Reavis <brian@thirdroute.com>
4071
+ */
4072
+
4073
+ Selectize.define('drag_drop', function(options) {
4074
+ if (!$.fn.sortable) throw new Error('The "drag_drop" plugin requires jQuery UI "sortable".');
4075
+ if (this.settings.mode !== 'multi') return;
4076
+ var self = this;
4077
+
4078
+ self.lock = (function() {
4079
+ var original = self.lock;
4080
+ return function() {
4081
+ var sortable = self.$control.data('sortable');
4082
+ if (sortable) sortable.disable();
4083
+ return original.apply(self, arguments);
4084
+ };
4085
+ })();
4086
+
4087
+ self.unlock = (function() {
4088
+ var original = self.unlock;
4089
+ return function() {
4090
+ var sortable = self.$control.data('sortable');
4091
+ if (sortable) sortable.enable();
4092
+ return original.apply(self, arguments);
4093
+ };
4094
+ })();
4095
+
4096
+ self.setup = (function() {
4097
+ var original = self.setup;
4098
+ return function() {
4099
+ original.apply(this, arguments);
4100
+
4101
+ var $control = self.$control.sortable({
4102
+ items: '[data-value]',
4103
+ forcePlaceholderSize: true,
4104
+ disabled: self.isLocked,
4105
+ start: function(e, ui) {
4106
+ ui.placeholder.css('width', ui.helper.css('width'));
4107
+ // $control.css({overflow: 'visible'});
4108
+ $control.addClass('dragging');
4109
+ },
4110
+ stop: function() {
4111
+ // $control.css({overflow: 'hidden'});
4112
+ $control.removeClass('dragging');
4113
+ var active = self.$activeItems ? self.$activeItems.slice() : null;
4114
+ var values = [];
4115
+ $control.children('[data-value]').each(function() {
4116
+ values.push($(this).attr('data-value'));
4117
+ });
4118
+ self.isFocused = false;
4119
+ self.setValue(values);
4120
+ self.isFocused = true;
4121
+ self.setActiveItem(active);
4122
+ self.positionDropdown();
4123
+ }
4124
+ });
4125
+ };
4126
+ })();
4127
+
4128
+ });
4129
+
4130
+ /**
4131
+ * Plugin: "dropdown_header" (selectize.js)
4132
+ * Copyright (c) 2013 Brian Reavis & contributors
4133
+ * Copyright (c) 2020-2022 Selectize Team & contributors
4134
+ *
4135
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
4136
+ * file except in compliance with the License. You may obtain a copy of the License at:
4137
+ * http://www.apache.org/licenses/LICENSE-2.0
4138
+ *
4139
+ * Unless required by applicable law or agreed to in writing, software distributed under
4140
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
4141
+ * ANY KIND, either express or implied. See the License for the specific language
4142
+ * governing permissions and limitations under the License.
4143
+ *
4144
+ * @author Brian Reavis <brian@thirdroute.com>
4145
+ */
4146
+
4147
+ Selectize.define('dropdown_header', function(options) {
4148
+ var self = this;
4149
+
4150
+ options = $.extend({
4151
+ title : 'Untitled',
4152
+ headerClass : 'selectize-dropdown-header',
4153
+ titleRowClass : 'selectize-dropdown-header-title',
4154
+ labelClass : 'selectize-dropdown-header-label',
4155
+ closeClass : 'selectize-dropdown-header-close',
4156
+
4157
+ html: function(data) {
4158
+ return (
4159
+ '<div class="' + data.headerClass + '">' +
4160
+ '<div class="' + data.titleRowClass + '">' +
4161
+ '<span class="' + data.labelClass + '">' + data.title + '</span>' +
4162
+ '<a href="javascript:void(0)" class="' + data.closeClass + '">&#xd7;</a>' +
4163
+ '</div>' +
4164
+ '</div>'
4165
+ );
4166
+ }
4167
+ }, options);
4168
+
4169
+ self.setup = (function() {
4170
+ var original = self.setup;
4171
+ return function() {
4172
+ original.apply(self, arguments);
4173
+ self.$dropdown_header = $(options.html(options));
4174
+ self.$dropdown.prepend(self.$dropdown_header);
4175
+ self.$dropdown_header.find('.' + options.closeClass).on('click', function () {
4176
+ self.close();
4177
+ });
4178
+ };
4179
+ })();
4180
+
4181
+ });
4182
+
4183
+ /**
4184
+ * Plugin: "optgroup_columns" (selectize.js)
4185
+ * Copyright (c) 2013 Simon Hewitt & contributors
4186
+ * Copyright (c) 2020-2022 Selectize Team & contributors*
4187
+ *
4188
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
4189
+ * file except in compliance with the License. You may obtain a copy of the License at:
4190
+ * http://www.apache.org/licenses/LICENSE-2.0
4191
+ *
4192
+ * Unless required by applicable law or agreed to in writing, software distributed under
4193
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
4194
+ * ANY KIND, either express or implied. See the License for the specific language
4195
+ * governing permissions and limitations under the License.
4196
+ *
4197
+ * @author Simon Hewitt <si@sjhewitt.co.uk>
4198
+ */
4199
+
4200
+ Selectize.define('optgroup_columns', function(options) {
4201
+ var self = this;
4202
+
4203
+ options = $.extend({
4204
+ equalizeWidth : true,
4205
+ equalizeHeight : true
4206
+ }, options);
4207
+
4208
+ this.getAdjacentOption = function($option, direction) {
4209
+ var $options = $option.closest('[data-group]').find('[data-selectable]');
4210
+ var index = $options.index($option) + direction;
4211
+
4212
+ return index >= 0 && index < $options.length ? $options.eq(index) : $();
4213
+ };
4214
+
4215
+ this.onKeyDown = (function() {
4216
+ var original = self.onKeyDown;
4217
+ return function(e) {
4218
+ var index, $option, $options, $optgroup;
4219
+
4220
+ if (this.isOpen && (e.keyCode === KEY_LEFT || e.keyCode === KEY_RIGHT)) {
4221
+ self.ignoreHover = true;
4222
+ $optgroup = this.$activeOption.closest('[data-group]');
4223
+ index = $optgroup.find('[data-selectable]').index(this.$activeOption);
4224
+
4225
+ if(e.keyCode === KEY_LEFT) {
4226
+ $optgroup = $optgroup.prev('[data-group]');
4227
+ } else {
4228
+ $optgroup = $optgroup.next('[data-group]');
4229
+ }
4230
+
4231
+ $options = $optgroup.find('[data-selectable]');
4232
+ $option = $options.eq(Math.min($options.length - 1, index));
4233
+ if ($option.length) {
4234
+ this.setActiveOption($option);
4235
+ }
4236
+ return;
4237
+ }
4238
+
4239
+ return original.apply(this, arguments);
4240
+ };
4241
+ })();
4242
+
4243
+ var getScrollbarWidth = function() {
4244
+ var div;
4245
+ var width = getScrollbarWidth.width;
4246
+ var doc = document;
4247
+
4248
+ if (typeof width === 'undefined') {
4249
+ div = doc.createElement('div');
4250
+ div.innerHTML = '<div style="width:50px;height:50px;position:absolute;left:-50px;top:-50px;overflow:auto;"><div style="width:1px;height:100px;"></div></div>';
4251
+ div = div.firstChild;
4252
+ doc.body.appendChild(div);
4253
+ width = getScrollbarWidth.width = div.offsetWidth - div.clientWidth;
4254
+ doc.body.removeChild(div);
4255
+ }
4256
+ return width;
4257
+ };
4258
+
4259
+ var equalizeSizes = function() {
4260
+ var i, n, height_max, width, width_last, width_parent, $optgroups;
4261
+
4262
+ $optgroups = $('[data-group]', self.$dropdown_content);
4263
+ n = $optgroups.length;
4264
+ if (!n || !self.$dropdown_content.width()) return;
4265
+
4266
+ if (options.equalizeHeight) {
4267
+ height_max = 0;
4268
+ for (i = 0; i < n; i++) {
4269
+ height_max = Math.max(height_max, $optgroups.eq(i).height());
4270
+ }
4271
+ $optgroups.css({height: height_max});
4272
+ }
4273
+
4274
+ if (options.equalizeWidth) {
4275
+ width_parent = self.$dropdown_content.innerWidth() - getScrollbarWidth();
4276
+ width = Math.round(width_parent / n);
4277
+ $optgroups.css({width: width});
4278
+ if (n > 1) {
4279
+ width_last = width_parent - width * (n - 1);
4280
+ $optgroups.eq(n - 1).css({width: width_last});
4281
+ }
4282
+ }
4283
+ };
4284
+
4285
+ if (options.equalizeHeight || options.equalizeWidth) {
4286
+ hook.after(this, 'positionDropdown', equalizeSizes);
4287
+ hook.after(this, 'refreshOptions', equalizeSizes);
4288
+ }
4289
+
4290
+
4291
+ });
4292
+
4293
+ /**
4294
+ * Plugin: "remove_button" (selectize.js)
4295
+ * Copyright (c) 2013 Brian Reavis & contributors
4296
+ * Copyright (c) 2020-2022 Selectize Team & contributors
4297
+ *
4298
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
4299
+ * file except in compliance with the License. You may obtain a copy of the License at:
4300
+ * http://www.apache.org/licenses/LICENSE-2.0
4301
+ *
4302
+ * Unless required by applicable law or agreed to in writing, software distributed under
4303
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
4304
+ * ANY KIND, either express or implied. See the License for the specific language
4305
+ * governing permissions and limitations under the License.
4306
+ *
4307
+ * @author Brian Reavis <brian@thirdroute.com>
4308
+ */
4309
+
4310
+ Selectize.define('remove_button', function (options) {
4311
+ if (this.settings.mode === 'single') return;
4312
+
4313
+ options = $.extend({
4314
+ label : '&#xd7;',
4315
+ title : 'Remove',
4316
+ className : 'remove',
4317
+ append : true
4318
+ }, options);
4319
+
4320
+ var multiClose = function(thisRef, options) {
4321
+
4322
+ var self = thisRef;
4323
+ var html = '<a href="javascript:void(0)" class="' + options.className + '" tabindex="-1" title="' + escape_html(options.title) + '">' + options.label + '</a>';
4324
+
4325
+ /**
4326
+ * Appends an element as a child (with raw HTML).
4327
+ *
4328
+ * @param {string} html_container
4329
+ * @param {string} html_element
4330
+ * @return {string}
4331
+ */
4332
+ var append = function(html_container, html_element) {
4333
+ var pos = html_container.search(/(<\/[^>]+>\s*)$/);
4334
+ return html_container.substring(0, pos) + html_element + html_container.substring(pos);
4335
+ };
4336
+
4337
+ thisRef.setup = (function() {
4338
+ var original = self.setup;
4339
+ return function() {
4340
+ // override the item rendering method to add the button to each
4341
+ if (options.append) {
4342
+ var render_item = self.settings.render.item;
4343
+ self.settings.render.item = function(data) {
4344
+ return append(render_item.apply(thisRef, arguments), html);
4345
+ };
4346
+ }
4347
+
4348
+ original.apply(thisRef, arguments);
4349
+
4350
+ // add event listener
4351
+ thisRef.$control.on('click', '.' + options.className, function(e) {
4352
+ e.preventDefault();
4353
+ if (self.isLocked) return;
4354
+
4355
+ var $item = $(e.currentTarget).parent();
4356
+ self.setActiveItem($item);
4357
+ if (self.deleteSelection()) {
4358
+ self.setCaret(self.items.length);
4359
+ }
4360
+ return false;
4361
+ });
4362
+
4363
+ };
4364
+ })();
4365
+ };
4366
+
4367
+ multiClose(this, options);
4368
+ });
4369
+
4370
+ /**
4371
+ * Plugin: "restore_on_backspace" (selectize.js)
4372
+ * Copyright (c) 2013 Brian Reavis & contributors
4373
+ * Copyright (c) 2020-2022 Selectize Team & contributors
4374
+ *
4375
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
4376
+ * file except in compliance with the License. You may obtain a copy of the License at:
4377
+ * http://www.apache.org/licenses/LICENSE-2.0
4378
+ *
4379
+ * Unless required by applicable law or agreed to in writing, software distributed under
4380
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
4381
+ * ANY KIND, either express or implied. See the License for the specific language
4382
+ * governing permissions and limitations under the License.
4383
+ *
4384
+ * @author Brian Reavis <brian@thirdroute.com>
4385
+ */
4386
+
4387
+ Selectize.define('restore_on_backspace', function(options) {
4388
+ var self = this;
4389
+
4390
+ options.text = options.text || function(option) {
4391
+ return option[this.settings.labelField];
4392
+ };
4393
+
4394
+ this.onKeyDown = (function() {
4395
+ var original = self.onKeyDown;
4396
+ return function(e) {
4397
+ var index, option;
4398
+ if (e.keyCode === KEY_BACKSPACE && this.$control_input.val() === '' && !this.$activeItems.length) {
4399
+ index = this.caretPos - 1;
4400
+ if (index >= 0 && index < this.items.length) {
4401
+ option = this.options[this.items[index]];
4402
+ if (this.deleteSelection(e)) {
4403
+ this.setTextboxValue(options.text.apply(this, [option]));
4404
+ this.refreshOptions(true);
4405
+ }
4406
+ e.preventDefault();
4407
+ return;
4408
+ }
4409
+ }
4410
+ return original.apply(this, arguments);
4411
+ };
4412
+ })();
4413
+ });
4414
+
4415
+ Selectize.define('select_on_focus', function(options) {
4416
+ var self = this;
4417
+
4418
+ self.on('focus', function() {
4419
+ var originalFocus = self.onFocus;
4420
+ return function(e) {
4421
+ var value = self.getItem(self.getValue()).text();
4422
+ self.clear();
4423
+ self.setTextboxValue(value);
4424
+ self.$control_input.select();
4425
+ setTimeout( function () {
4426
+ if (self.settings.selectOnTab) {
4427
+ self.setActiveOption(self.getFirstItemMatchedByTextContent(value));
4428
+ }
4429
+ self.settings.score = null;
4430
+ },0);
4431
+ return originalFocus.apply(this, arguments);
4432
+ };
4433
+ }());
4434
+
4435
+ self.onBlur = (function() {
4436
+ var originalBlur = self.onBlur;
4437
+ return function(e) {
4438
+ if (self.getValue() === "" && self.lastValidValue !== self.getValue()) {
4439
+ self.setValue(self.lastValidValue);
4440
+ }
4441
+ setTimeout( function () {
4442
+ self.settings.score = function() {
4443
+ return function() {
4444
+ return 1;
4445
+ };
4446
+ };
4447
+ }, 0 );
4448
+ return originalBlur.apply(this, arguments);
4449
+ }
4450
+ }());
4451
+ self.settings.score = function() {
4452
+ return function() { return 1; };
4453
+ };
4454
+
4455
+ });
4456
+
4457
+ Selectize.define('tag_limit', function (options) {
4458
+ const self = this
4459
+ options.tagLimit = options.tagLimit
4460
+ this.onBlur = (function (e) {
4461
+ const original = self.onBlur
4462
+
4463
+ return function (e) {
4464
+ original.apply(this, e);
4465
+ if (!e)
4466
+ return
4467
+ const $control = this.$control
4468
+ const $items = $control.find('.item')
4469
+ const limit = options.tagLimit
4470
+ if (limit === undefined || $items.length <= limit)
4471
+ return
4472
+
4473
+ $items.toArray().forEach(function(item, index) {
4474
+ if (index < limit)
4475
+ return
4476
+ $(item).hide()
4477
+ });
4478
+
4479
+ $control.append('<span><b>' + ($items.length - limit) + '</b></span>')
4480
+ };
4481
+ })()
4482
+
4483
+ this.onFocus = (function (e) {
4484
+ const original = self.onFocus
4485
+
4486
+ return function (e) {
4487
+ original.apply(this, e);
4488
+ if (!e)
4489
+ return
4490
+ const $control = this.$control
4491
+ const $items = $control.find('.item')
4492
+ $items.show()
4493
+ $control.find('span').remove()
4494
+
4495
+ };
4496
+ })()
4497
+ });
4498
+
4499
+ return Selectize;
4500
+ }));