rails-active-ui 0.2.2 → 0.3.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 (226) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/datatables.css +15 -0
  3. data/app/assets/stylesheets.css +5 -1
  4. data/app/blocks/resource_list_block.rb +153 -0
  5. data/app/components/back_button_component.rb +34 -0
  6. data/app/components/button_component.rb +4 -2
  7. data/app/components/button_to_component.rb +3 -4
  8. data/app/components/column_component.rb +1 -1
  9. data/app/components/container_component.rb +1 -1
  10. data/app/components/dropdown_component.rb +8 -2
  11. data/app/components/grid_component.rb +5 -1
  12. data/app/components/link_to_component.rb +23 -0
  13. data/app/components/menu_item_component.rb +5 -1
  14. data/app/components/message_component.rb +3 -1
  15. data/app/components/modal_component.rb +23 -3
  16. data/app/components/paragraph_component.rb +13 -0
  17. data/app/components/row_component.rb +1 -1
  18. data/app/components/table_row_component.rb +3 -5
  19. data/app/components/template_component.rb +13 -0
  20. data/app/helpers/component_helper.rb +122 -76
  21. data/app/helpers/fui_helper.rb +37 -0
  22. data/app/javascript/datatables.js +10 -0
  23. data/app/javascript/ui/controllers/fui_datatable_controller.js +35 -0
  24. data/app/javascript/ui/controllers/fui_dropdown_controller.js +8 -1
  25. data/app/javascript/ui/controllers/fui_item_list_controller.js +40 -0
  26. data/app/javascript/ui/controllers/navigation_controller.js +23 -0
  27. data/app/javascript/ui/index.js +11 -0
  28. data/app/lib/component.rb +1 -1
  29. data/config/importmap.rb +3 -0
  30. data/config/initializers/ruby_template_handler.rb +4 -1
  31. data/formantic-ui/components/accordion.css +369 -0
  32. data/formantic-ui/components/accordion.js +595 -0
  33. data/formantic-ui/components/accordion.min.css +9 -0
  34. data/formantic-ui/components/accordion.min.js +11 -0
  35. data/formantic-ui/components/ad.css +264 -0
  36. data/formantic-ui/components/ad.min.css +10 -0
  37. data/formantic-ui/components/api.js +1225 -0
  38. data/formantic-ui/components/api.min.js +11 -0
  39. data/formantic-ui/components/breadcrumb.css +135 -0
  40. data/formantic-ui/components/breadcrumb.min.css +9 -0
  41. data/formantic-ui/components/button.css +4058 -0
  42. data/formantic-ui/components/button.min.css +9 -0
  43. data/formantic-ui/components/calendar.css +327 -0
  44. data/formantic-ui/components/calendar.js +2045 -0
  45. data/formantic-ui/components/calendar.min.css +9 -0
  46. data/formantic-ui/components/calendar.min.js +11 -0
  47. data/formantic-ui/components/card.css +1881 -0
  48. data/formantic-ui/components/card.min.css +9 -0
  49. data/formantic-ui/components/checkbox.css +785 -0
  50. data/formantic-ui/components/checkbox.js +888 -0
  51. data/formantic-ui/components/checkbox.min.css +9 -0
  52. data/formantic-ui/components/checkbox.min.js +11 -0
  53. data/formantic-ui/components/comment.css +283 -0
  54. data/formantic-ui/components/comment.min.css +9 -0
  55. data/formantic-ui/components/container.css +300 -0
  56. data/formantic-ui/components/container.min.css +9 -0
  57. data/formantic-ui/components/dimmer.css +367 -0
  58. data/formantic-ui/components/dimmer.js +732 -0
  59. data/formantic-ui/components/dimmer.min.css +9 -0
  60. data/formantic-ui/components/dimmer.min.js +11 -0
  61. data/formantic-ui/components/divider.css +287 -0
  62. data/formantic-ui/components/divider.min.css +9 -0
  63. data/formantic-ui/components/dropdown.css +2087 -0
  64. data/formantic-ui/components/dropdown.js +4432 -0
  65. data/formantic-ui/components/dropdown.min.css +9 -0
  66. data/formantic-ui/components/dropdown.min.js +11 -0
  67. data/formantic-ui/components/embed.css +155 -0
  68. data/formantic-ui/components/embed.js +688 -0
  69. data/formantic-ui/components/embed.min.css +9 -0
  70. data/formantic-ui/components/embed.min.js +11 -0
  71. data/formantic-ui/components/emoji.css +15311 -0
  72. data/formantic-ui/components/emoji.min.css +9 -0
  73. data/formantic-ui/components/feed.css +799 -0
  74. data/formantic-ui/components/feed.min.css +9 -0
  75. data/formantic-ui/components/flag.css +1149 -0
  76. data/formantic-ui/components/flag.min.css +9 -0
  77. data/formantic-ui/components/flyout.css +546 -0
  78. data/formantic-ui/components/flyout.js +1551 -0
  79. data/formantic-ui/components/flyout.min.css +9 -0
  80. data/formantic-ui/components/flyout.min.js +11 -0
  81. data/formantic-ui/components/form.css +1885 -0
  82. data/formantic-ui/components/form.js +2199 -0
  83. data/formantic-ui/components/form.min.css +9 -0
  84. data/formantic-ui/components/form.min.js +11 -0
  85. data/formantic-ui/components/grid.css +1952 -0
  86. data/formantic-ui/components/grid.min.css +9 -0
  87. data/formantic-ui/components/header.css +778 -0
  88. data/formantic-ui/components/header.min.css +9 -0
  89. data/formantic-ui/components/icon.css +7066 -0
  90. data/formantic-ui/components/icon.min.css +9 -0
  91. data/formantic-ui/components/image.css +315 -0
  92. data/formantic-ui/components/image.min.css +9 -0
  93. data/formantic-ui/components/input.css +1566 -0
  94. data/formantic-ui/components/input.min.css +9 -0
  95. data/formantic-ui/components/item.css +534 -0
  96. data/formantic-ui/components/item.min.css +9 -0
  97. data/formantic-ui/components/label.css +2114 -0
  98. data/formantic-ui/components/label.min.css +9 -0
  99. data/formantic-ui/components/list.css +955 -0
  100. data/formantic-ui/components/list.min.css +9 -0
  101. data/formantic-ui/components/loader.css +787 -0
  102. data/formantic-ui/components/loader.min.css +9 -0
  103. data/formantic-ui/components/menu.css +2131 -0
  104. data/formantic-ui/components/menu.min.css +9 -0
  105. data/formantic-ui/components/message.css +619 -0
  106. data/formantic-ui/components/message.min.css +9 -0
  107. data/formantic-ui/components/modal.css +779 -0
  108. data/formantic-ui/components/modal.js +1637 -0
  109. data/formantic-ui/components/modal.min.css +9 -0
  110. data/formantic-ui/components/modal.min.js +11 -0
  111. data/formantic-ui/components/nag.css +290 -0
  112. data/formantic-ui/components/nag.js +566 -0
  113. data/formantic-ui/components/nag.min.css +9 -0
  114. data/formantic-ui/components/nag.min.js +11 -0
  115. data/formantic-ui/components/placeholder.css +228 -0
  116. data/formantic-ui/components/placeholder.min.css +9 -0
  117. data/formantic-ui/components/popup.css +1184 -0
  118. data/formantic-ui/components/popup.js +1561 -0
  119. data/formantic-ui/components/popup.min.css +9 -0
  120. data/formantic-ui/components/popup.min.js +11 -0
  121. data/formantic-ui/components/progress.css +761 -0
  122. data/formantic-ui/components/progress.js +979 -0
  123. data/formantic-ui/components/progress.min.css +9 -0
  124. data/formantic-ui/components/progress.min.js +11 -0
  125. data/formantic-ui/components/rail.css +147 -0
  126. data/formantic-ui/components/rail.min.css +9 -0
  127. data/formantic-ui/components/rating.css +414 -0
  128. data/formantic-ui/components/rating.js +540 -0
  129. data/formantic-ui/components/rating.min.css +9 -0
  130. data/formantic-ui/components/rating.min.js +11 -0
  131. data/formantic-ui/components/reset.css +386 -0
  132. data/formantic-ui/components/reset.min.css +9 -0
  133. data/formantic-ui/components/reveal.css +277 -0
  134. data/formantic-ui/components/reveal.min.css +9 -0
  135. data/formantic-ui/components/search.css +541 -0
  136. data/formantic-ui/components/search.js +1641 -0
  137. data/formantic-ui/components/search.min.css +9 -0
  138. data/formantic-ui/components/search.min.js +11 -0
  139. data/formantic-ui/components/segment.css +1053 -0
  140. data/formantic-ui/components/segment.min.css +9 -0
  141. data/formantic-ui/components/shape.css +144 -0
  142. data/formantic-ui/components/shape.js +797 -0
  143. data/formantic-ui/components/shape.min.css +9 -0
  144. data/formantic-ui/components/shape.min.js +11 -0
  145. data/formantic-ui/components/sidebar.css +539 -0
  146. data/formantic-ui/components/sidebar.js +1054 -0
  147. data/formantic-ui/components/sidebar.min.css +9 -0
  148. data/formantic-ui/components/sidebar.min.js +11 -0
  149. data/formantic-ui/components/site.css +286 -0
  150. data/formantic-ui/components/site.js +455 -0
  151. data/formantic-ui/components/site.min.css +9 -0
  152. data/formantic-ui/components/site.min.js +11 -0
  153. data/formantic-ui/components/slider.css +926 -0
  154. data/formantic-ui/components/slider.js +1546 -0
  155. data/formantic-ui/components/slider.min.css +9 -0
  156. data/formantic-ui/components/slider.min.js +11 -0
  157. data/formantic-ui/components/state.js +697 -0
  158. data/formantic-ui/components/state.min.js +11 -0
  159. data/formantic-ui/components/statistic.css +586 -0
  160. data/formantic-ui/components/statistic.min.css +9 -0
  161. data/formantic-ui/components/step.css +1538 -0
  162. data/formantic-ui/components/step.min.css +9 -0
  163. data/formantic-ui/components/sticky.css +73 -0
  164. data/formantic-ui/components/sticky.js +917 -0
  165. data/formantic-ui/components/sticky.min.css +9 -0
  166. data/formantic-ui/components/sticky.min.js +11 -0
  167. data/formantic-ui/components/tab.css +84 -0
  168. data/formantic-ui/components/tab.js +967 -0
  169. data/formantic-ui/components/tab.min.css +9 -0
  170. data/formantic-ui/components/tab.min.js +11 -0
  171. data/formantic-ui/components/table.css +3473 -0
  172. data/formantic-ui/components/table.min.css +9 -0
  173. data/formantic-ui/components/text.css +155 -0
  174. data/formantic-ui/components/text.min.css +9 -0
  175. data/formantic-ui/components/toast.css +751 -0
  176. data/formantic-ui/components/toast.js +964 -0
  177. data/formantic-ui/components/toast.min.css +9 -0
  178. data/formantic-ui/components/toast.min.js +11 -0
  179. data/formantic-ui/components/transition.css +1148 -0
  180. data/formantic-ui/components/transition.js +1034 -0
  181. data/formantic-ui/components/transition.min.css +9 -0
  182. data/formantic-ui/components/transition.min.js +11 -0
  183. data/formantic-ui/components/visibility.js +1292 -0
  184. data/formantic-ui/components/visibility.min.js +11 -0
  185. data/formantic-ui/semantic.css +78485 -0
  186. data/formantic-ui/semantic.js +31036 -0
  187. data/formantic-ui/semantic.min.css +11 -0
  188. data/formantic-ui/semantic.min.js +11 -0
  189. data/formantic-ui/themes/basic/assets/fonts/LICENSE.txt +91 -0
  190. data/formantic-ui/themes/basic/assets/fonts/icons.woff +0 -0
  191. data/formantic-ui/themes/basic/assets/fonts/icons.woff2 +0 -0
  192. data/formantic-ui/themes/default/assets/fonts/LICENSE_Lato.txt +94 -0
  193. data/formantic-ui/themes/default/assets/fonts/LICENSE_icons.txt +165 -0
  194. data/formantic-ui/themes/default/assets/fonts/Lato-Bold.woff +0 -0
  195. data/formantic-ui/themes/default/assets/fonts/Lato-Bold.woff2 +0 -0
  196. data/formantic-ui/themes/default/assets/fonts/Lato-BoldItalic.woff +0 -0
  197. data/formantic-ui/themes/default/assets/fonts/Lato-BoldItalic.woff2 +0 -0
  198. data/formantic-ui/themes/default/assets/fonts/Lato-Italic.woff +0 -0
  199. data/formantic-ui/themes/default/assets/fonts/Lato-Italic.woff2 +0 -0
  200. data/formantic-ui/themes/default/assets/fonts/Lato-Regular.woff +0 -0
  201. data/formantic-ui/themes/default/assets/fonts/Lato-Regular.woff2 +0 -0
  202. data/formantic-ui/themes/default/assets/fonts/LatoLatin-Bold.woff +0 -0
  203. data/formantic-ui/themes/default/assets/fonts/LatoLatin-Bold.woff2 +0 -0
  204. data/formantic-ui/themes/default/assets/fonts/LatoLatin-BoldItalic.woff +0 -0
  205. data/formantic-ui/themes/default/assets/fonts/LatoLatin-BoldItalic.woff2 +0 -0
  206. data/formantic-ui/themes/default/assets/fonts/LatoLatin-Italic.woff +0 -0
  207. data/formantic-ui/themes/default/assets/fonts/LatoLatin-Italic.woff2 +0 -0
  208. data/formantic-ui/themes/default/assets/fonts/LatoLatin-Regular.woff +0 -0
  209. data/formantic-ui/themes/default/assets/fonts/LatoLatin-Regular.woff2 +0 -0
  210. data/formantic-ui/themes/default/assets/fonts/brand-icons.woff +0 -0
  211. data/formantic-ui/themes/default/assets/fonts/brand-icons.woff2 +0 -0
  212. data/formantic-ui/themes/default/assets/fonts/icons.woff +0 -0
  213. data/formantic-ui/themes/default/assets/fonts/icons.woff2 +0 -0
  214. data/formantic-ui/themes/default/assets/fonts/outline-icons.woff +0 -0
  215. data/formantic-ui/themes/default/assets/fonts/outline-icons.woff2 +0 -0
  216. data/formantic-ui/themes/famfamfam/assets/images/flags.png +0 -0
  217. data/formantic-ui/themes/github/assets/fonts/LICENSE.txt +94 -0
  218. data/formantic-ui/themes/github/assets/fonts/octicons.woff +0 -0
  219. data/formantic-ui/themes/github/assets/fonts/octicons.woff2 +0 -0
  220. data/formantic-ui/themes/material/assets/fonts/LICENSE.txt +202 -0
  221. data/formantic-ui/themes/material/assets/fonts/icons.woff +0 -0
  222. data/formantic-ui/themes/material/assets/fonts/icons.woff2 +0 -0
  223. data/lib/ui/engine.rb +7 -2
  224. data/lib/ui/version.rb +1 -1
  225. metadata +205 -7
  226. data/app/components/link_component.rb +0 -23
@@ -0,0 +1,1641 @@
1
+ /*!
2
+ * # Fomantic-UI 2.9.4 - Search
3
+ * https://github.com/fomantic/Fomantic-UI/
4
+ *
5
+ *
6
+ * Released under the MIT license
7
+ * https://opensource.org/licenses/MIT
8
+ *
9
+ */
10
+
11
+ (function ($, window, document) {
12
+ 'use strict';
13
+
14
+ function isFunction(obj) {
15
+ return typeof obj === 'function' && typeof obj.nodeType !== 'number';
16
+ }
17
+
18
+ window = window !== undefined && window.Math === Math
19
+ ? window
20
+ : globalThis;
21
+
22
+ $.fn.search = function (parameters) {
23
+ var
24
+ $allModules = $(this),
25
+
26
+ time = Date.now(),
27
+ performance = [],
28
+
29
+ query = arguments[0],
30
+ methodInvoked = typeof query === 'string',
31
+ queryArguments = [].slice.call(arguments, 1),
32
+ returnedValue
33
+ ;
34
+ $allModules.each(function () {
35
+ var
36
+ settings = $.isPlainObject(parameters)
37
+ ? $.extend(true, {}, $.fn.search.settings, parameters)
38
+ : $.extend({}, $.fn.search.settings),
39
+
40
+ className = settings.className,
41
+ metadata = settings.metadata,
42
+ regExp = settings.regExp,
43
+ fields = settings.fields,
44
+ selector = settings.selector,
45
+ error = settings.error,
46
+ namespace = settings.namespace,
47
+
48
+ eventNamespace = '.' + namespace,
49
+ moduleNamespace = namespace + '-module',
50
+
51
+ $module = $(this),
52
+ $prompt = $module.find(selector.prompt),
53
+ $searchButton = $module.find(selector.searchButton),
54
+ $results = $module.find(selector.results),
55
+ $result = $module.find(selector.result),
56
+ $category = $module.find(selector.category),
57
+
58
+ element = this,
59
+ instance = $module.data(moduleNamespace),
60
+
61
+ disabledBubbled = false,
62
+ resultsDismissed = false,
63
+
64
+ module
65
+ ;
66
+
67
+ module = {
68
+
69
+ initialize: function () {
70
+ module.verbose('Initializing module');
71
+ module.get.settings();
72
+ module.determine.searchFields();
73
+ module.bind.events();
74
+ module.set.type();
75
+ module.create.results();
76
+ module.instantiate();
77
+ },
78
+ instantiate: function () {
79
+ module.verbose('Storing instance of module', module);
80
+ instance = module;
81
+ $module
82
+ .data(moduleNamespace, module)
83
+ ;
84
+ },
85
+ destroy: function () {
86
+ module.verbose('Destroying instance');
87
+ $module
88
+ .off(eventNamespace)
89
+ .removeData(moduleNamespace)
90
+ ;
91
+ },
92
+
93
+ refresh: function () {
94
+ module.debug('Refreshing selector cache');
95
+ $prompt = $module.find(selector.prompt);
96
+ $searchButton = $module.find(selector.searchButton);
97
+ $category = $module.find(selector.category);
98
+ $results = $module.find(selector.results);
99
+ $result = $module.find(selector.result);
100
+ },
101
+
102
+ refreshResults: function () {
103
+ $results = $module.find(selector.results);
104
+ $result = $module.find(selector.result);
105
+ },
106
+
107
+ bind: {
108
+ events: function () {
109
+ module.verbose('Binding events to search');
110
+ if (settings.automatic) {
111
+ $module
112
+ .on(module.get.inputEvent() + eventNamespace, selector.prompt, module.event.input)
113
+ ;
114
+ $prompt
115
+ .attr('autocomplete', module.is.chrome() ? 'fomantic-search' : 'off')
116
+ ;
117
+ }
118
+ $module
119
+ // prompt
120
+ .on('focus' + eventNamespace, selector.prompt, module.event.focus)
121
+ .on('blur' + eventNamespace, selector.prompt, module.event.blur)
122
+ .on('keydown' + eventNamespace, selector.prompt, module.handleKeyboard)
123
+ // search button
124
+ .on('click' + eventNamespace, selector.searchButton, module.query)
125
+ // results
126
+ .on('mousedown' + eventNamespace, selector.results, module.event.result.mousedown)
127
+ .on('mouseup' + eventNamespace, selector.results, module.event.result.mouseup)
128
+ .on('click' + eventNamespace, selector.result, module.event.result.click)
129
+ .on('click' + eventNamespace, selector.remove, module.event.remove.click)
130
+ ;
131
+ },
132
+ },
133
+
134
+ determine: {
135
+ searchFields: function () {
136
+ // this makes sure $.extend does not add specified search fields to default fields
137
+ // this is the only setting which should not extend defaults
138
+ if (parameters && parameters.searchFields !== undefined) {
139
+ settings.searchFields = Array.isArray(parameters.searchFields)
140
+ ? parameters.searchFields
141
+ : [parameters.searchFields]
142
+ ;
143
+ }
144
+ },
145
+ },
146
+
147
+ event: {
148
+ input: function () {
149
+ if (settings.searchDelay) {
150
+ clearTimeout(module.timer);
151
+ module.timer = setTimeout(function () {
152
+ if (module.is.focused()) {
153
+ module.query();
154
+ }
155
+ }, settings.searchDelay);
156
+ } else {
157
+ module.query();
158
+ }
159
+ },
160
+ focus: function () {
161
+ module.set.focus();
162
+ if (settings.searchOnFocus && module.has.minimumCharacters()) {
163
+ module.query(function () {
164
+ if (module.can.show()) {
165
+ module.showResults();
166
+ }
167
+ });
168
+ }
169
+ },
170
+ blur: function (event) {
171
+ var
172
+ pageLostFocus = document.activeElement === this,
173
+ callback = function () {
174
+ module.cancel.query();
175
+ module.remove.focus();
176
+ module.timer = setTimeout(function () {
177
+ module.hideResults();
178
+ }, settings.hideDelay);
179
+ }
180
+ ;
181
+ if (pageLostFocus) {
182
+ return;
183
+ }
184
+ resultsDismissed = false;
185
+ if (module.resultsClicked) {
186
+ module.debug('Determining if user action caused search to close');
187
+ $module
188
+ .one('click.close' + eventNamespace, selector.results, function (event) {
189
+ if (module.is.inMessage(event) || disabledBubbled) {
190
+ $prompt.trigger('focus');
191
+
192
+ return;
193
+ }
194
+ disabledBubbled = false;
195
+ if (!module.is.animating() && !module.is.hidden()) {
196
+ callback();
197
+ }
198
+ })
199
+ ;
200
+ } else {
201
+ module.debug('Input blurred without user action, closing results');
202
+ callback();
203
+ }
204
+ },
205
+ remove: {
206
+ click: function () {
207
+ module.clear.value();
208
+ $prompt.trigger('focus');
209
+ },
210
+ },
211
+ result: {
212
+ mousedown: function () {
213
+ module.resultsClicked = true;
214
+ },
215
+ mouseup: function () {
216
+ module.resultsClicked = false;
217
+ },
218
+ click: function (event) {
219
+ module.debug('Search result selected');
220
+ var
221
+ $result = $(this),
222
+ $title = $result.find(selector.title).eq(0),
223
+ $link = $result.is('a[href]')
224
+ ? $result
225
+ : $result.find('a[href]').eq(0),
226
+ href = $link.attr('href') || false,
227
+ target = $link.attr('target') || false,
228
+ // title is used for result lookup
229
+ value = $title.length > 0
230
+ ? $title.text()
231
+ : false,
232
+ results = module.get.results(),
233
+ result = $result.data(metadata.result) || module.get.result(value, results)
234
+ ;
235
+ var oldValue = module.get.value();
236
+ if (isFunction(settings.onSelect)) {
237
+ if (settings.onSelect.call(element, result, results) === false) {
238
+ module.debug('Custom onSelect callback cancelled default select action');
239
+ disabledBubbled = true;
240
+
241
+ return;
242
+ }
243
+ }
244
+ module.hideResults();
245
+ if (value && module.get.value() === oldValue) {
246
+ module.set.value(value);
247
+ }
248
+ if (href) {
249
+ event.preventDefault();
250
+ module.verbose('Opening search link found in result', $link);
251
+ if (target === '_blank' || event.ctrlKey) {
252
+ window.open(href);
253
+ } else {
254
+ window.location.href = href;
255
+ }
256
+ }
257
+ },
258
+ },
259
+ },
260
+ ensureVisible: function ($el) {
261
+ var
262
+ elTop,
263
+ elBottom,
264
+ resultsScrollTop,
265
+ resultsHeight
266
+ ;
267
+ if ($el.length === 0) {
268
+ return;
269
+ }
270
+ elTop = $el.position().top;
271
+ elBottom = elTop + $el.outerHeight(true);
272
+
273
+ resultsScrollTop = $results.scrollTop();
274
+ resultsHeight = $results.height();
275
+
276
+ if (elTop < 0) {
277
+ $results.scrollTop(resultsScrollTop + elTop);
278
+ } else if (resultsHeight < elBottom) {
279
+ $results.scrollTop(resultsScrollTop + (elBottom - resultsHeight));
280
+ }
281
+ },
282
+ handleKeyboard: function (event) {
283
+ var
284
+ // force selector refresh
285
+ $result = $module.find(selector.result),
286
+ $category = $module.find(selector.category),
287
+ $activeResult = $result.filter('.' + className.active),
288
+ currentIndex = $result.index($activeResult),
289
+ resultSize = $result.length,
290
+ hasActiveResult = $activeResult.length > 0,
291
+
292
+ keyCode = event.which,
293
+ keys = {
294
+ backspace: 8,
295
+ enter: 13,
296
+ escape: 27,
297
+ upArrow: 38,
298
+ downArrow: 40,
299
+ },
300
+ newIndex
301
+ ;
302
+ // search shortcuts
303
+ if (keyCode === keys.escape) {
304
+ if (!module.is.visible()) {
305
+ module.verbose('Escape key pressed, blurring search field');
306
+ $prompt.trigger('blur');
307
+ } else {
308
+ module.hideResults();
309
+ }
310
+ event.stopPropagation();
311
+ resultsDismissed = true;
312
+ }
313
+ if (module.is.visible()) {
314
+ if (keyCode === keys.enter) {
315
+ module.verbose('Enter key pressed, selecting active result');
316
+ if ($result.filter('.' + className.active).length > 0) {
317
+ module.event.result.click.call($result.filter('.' + className.active), event);
318
+ event.preventDefault();
319
+
320
+ return false;
321
+ }
322
+ } else if (keyCode === keys.upArrow && hasActiveResult) {
323
+ module.verbose('Up key pressed, changing active result');
324
+ newIndex = currentIndex - 1 < 0
325
+ ? currentIndex
326
+ : currentIndex - 1;
327
+ $category
328
+ .removeClass(className.active)
329
+ ;
330
+ $result
331
+ .removeClass(className.active)
332
+ .eq(newIndex)
333
+ .addClass(className.active)
334
+ .closest($category)
335
+ .addClass(className.active)
336
+ ;
337
+ module.ensureVisible($result.eq(newIndex));
338
+ event.preventDefault();
339
+ } else if (keyCode === keys.downArrow) {
340
+ module.verbose('Down key pressed, changing active result');
341
+ newIndex = currentIndex + 1 >= resultSize
342
+ ? currentIndex
343
+ : currentIndex + 1;
344
+ $category
345
+ .removeClass(className.active)
346
+ ;
347
+ $result
348
+ .removeClass(className.active)
349
+ .eq(newIndex)
350
+ .addClass(className.active)
351
+ .closest($category)
352
+ .addClass(className.active)
353
+ ;
354
+ module.ensureVisible($result.eq(newIndex));
355
+ event.preventDefault();
356
+ }
357
+ } else {
358
+ // query shortcuts
359
+ if (keyCode === keys.enter) {
360
+ module.verbose('Enter key pressed, executing query');
361
+ module.query();
362
+ module.set.buttonPressed();
363
+ $prompt.one('keyup', module.remove.buttonFocus);
364
+ }
365
+ }
366
+ },
367
+
368
+ setup: {
369
+ api: function (searchTerm, callback) {
370
+ var
371
+ apiSettings = {
372
+ debug: settings.debug,
373
+ on: false,
374
+ cache: settings.cache,
375
+ action: 'search',
376
+ urlData: {
377
+ query: searchTerm,
378
+ },
379
+ },
380
+ apiCallbacks = {
381
+ onSuccess: function (response, $module, xhr) {
382
+ module.parse.response.call(element, response, searchTerm);
383
+ callback();
384
+ if (settings.apiSettings && typeof settings.apiSettings.onSuccess === 'function') {
385
+ settings.apiSettings.onSuccess.call(this, response, $module, xhr);
386
+ }
387
+ },
388
+ onFailure: function (response, $module, xhr) {
389
+ module.displayMessage(error.serverError);
390
+ callback();
391
+ if (settings.apiSettings && typeof settings.apiSettings.onFailure === 'function') {
392
+ settings.apiSettings.onFailure.call(this, response, $module, xhr);
393
+ }
394
+ },
395
+ onAbort: function (status, $module, xhr) {
396
+ if (settings.apiSettings && typeof settings.apiSettings.onAbort === 'function') {
397
+ settings.apiSettings.onAbort.call(this, status, $module, xhr);
398
+ }
399
+ },
400
+ onError: function (errorMessage, $module, xhr) {
401
+ module.error();
402
+ if (settings.apiSettings && typeof settings.apiSettings.onError === 'function') {
403
+ settings.apiSettings.onError.call(this, errorMessage, $module, xhr);
404
+ }
405
+ },
406
+ }
407
+ ;
408
+ $.extend(true, apiSettings, settings.apiSettings, apiCallbacks);
409
+ module.verbose('Setting up API request', apiSettings);
410
+ $module.api(apiSettings);
411
+ },
412
+ },
413
+
414
+ can: {
415
+ useAPI: function () {
416
+ return $.fn.api !== undefined;
417
+ },
418
+ show: function () {
419
+ return module.is.focused() && !module.is.visible() && !module.is.empty();
420
+ },
421
+ transition: function () {
422
+ return settings.transition && $.fn.transition !== undefined;
423
+ },
424
+ },
425
+
426
+ is: {
427
+ animating: function () {
428
+ return $results.hasClass(className.animating);
429
+ },
430
+ chrome: function () {
431
+ return !!window.chrome && !window.StyleMedia;
432
+ },
433
+ hidden: function () {
434
+ return $results.hasClass(className.hidden);
435
+ },
436
+ inMessage: function (event) {
437
+ if (!event.target) {
438
+ return;
439
+ }
440
+ var
441
+ $target = $(event.target),
442
+ isInDOM = $.contains(document.documentElement, event.target)
443
+ ;
444
+
445
+ return isInDOM && $target.closest(selector.message).length > 0;
446
+ },
447
+ empty: function () {
448
+ return $results.html() === '';
449
+ },
450
+ visible: function () {
451
+ return $results.filter(':visible').length > 0;
452
+ },
453
+ focused: function () {
454
+ return $prompt.filter(':focus').length > 0;
455
+ },
456
+ },
457
+
458
+ get: {
459
+ settings: function () {
460
+ if ($.isPlainObject(parameters) && parameters.searchFullText) {
461
+ settings.fullTextSearch = parameters.searchFullText;
462
+ module.error(settings.error.oldSearchSyntax, element);
463
+ }
464
+ if (settings.ignoreDiacritics && !String.prototype.normalize) {
465
+ settings.ignoreDiacritics = false;
466
+ module.error(error.noNormalize, element);
467
+ }
468
+ },
469
+ inputEvent: function () {
470
+ var
471
+ prompt = $prompt[0],
472
+ inputEvent = prompt !== undefined && prompt.oninput !== undefined
473
+ ? 'input'
474
+ : (prompt !== undefined && prompt.onpropertychange !== undefined
475
+ ? 'propertychange'
476
+ : 'keyup')
477
+ ;
478
+
479
+ return inputEvent;
480
+ },
481
+ value: function () {
482
+ return $prompt.val();
483
+ },
484
+ results: function () {
485
+ return $module.data(metadata.results);
486
+ },
487
+ result: function (value, results) {
488
+ var
489
+ result = false
490
+ ;
491
+ value = value !== undefined
492
+ ? value
493
+ : module.get.value();
494
+ results = results !== undefined
495
+ ? results
496
+ : module.get.results();
497
+ if (settings.type === 'category') {
498
+ module.debug('Finding result that matches', value);
499
+ $.each(results, function (index, category) {
500
+ if (Array.isArray(category.results)) {
501
+ result = module.search.object(value, category.results)[0];
502
+ // don't continue searching if a result is found
503
+ if (result) {
504
+ return false;
505
+ }
506
+ }
507
+ });
508
+ } else {
509
+ module.debug('Finding result in results object', value);
510
+ result = module.search.object(value, results)[0];
511
+ }
512
+
513
+ return result || false;
514
+ },
515
+ },
516
+
517
+ select: {
518
+ firstResult: function () {
519
+ module.verbose('Selecting first result');
520
+ $result.first().addClass(className.active);
521
+ },
522
+ },
523
+
524
+ set: {
525
+ focus: function () {
526
+ $module.addClass(className.focus);
527
+ },
528
+ loading: function () {
529
+ $module.addClass(className.loading);
530
+ },
531
+ value: function (value) {
532
+ module.verbose('Setting search input value', value);
533
+ $prompt
534
+ .val(value)
535
+ ;
536
+ },
537
+ type: function (type) {
538
+ type = type || settings.type;
539
+ if (className[type]) {
540
+ $module.addClass(className[type]);
541
+ }
542
+ },
543
+ buttonPressed: function () {
544
+ $searchButton.addClass(className.pressed);
545
+ },
546
+ },
547
+
548
+ remove: {
549
+ loading: function () {
550
+ $module.removeClass(className.loading);
551
+ },
552
+ focus: function () {
553
+ $module.removeClass(className.focus);
554
+ },
555
+ buttonPressed: function () {
556
+ $searchButton.removeClass(className.pressed);
557
+ },
558
+ diacritics: function (text) {
559
+ return settings.ignoreDiacritics ? text.normalize('NFD').replace(/[\u0300-\u036F]/g, '') : text;
560
+ },
561
+ },
562
+
563
+ query: function (callback) {
564
+ callback = isFunction(callback)
565
+ ? callback
566
+ : function () {};
567
+ var
568
+ searchTerm = module.get.value(),
569
+ cache = module.read.cache(searchTerm)
570
+ ;
571
+ callback = callback || function () {};
572
+ if (module.has.minimumCharacters()) {
573
+ if (cache) {
574
+ module.debug('Reading result from cache', searchTerm);
575
+ module.save.results(cache.results);
576
+ settings.onResults.call(element, cache.results, true);
577
+ module.addResults(cache.html);
578
+ module.inject.id(cache.results);
579
+ callback();
580
+ } else {
581
+ module.debug('Querying for', searchTerm);
582
+ if ($.isPlainObject(settings.source) || Array.isArray(settings.source)) {
583
+ module.search.local(searchTerm);
584
+ callback();
585
+ } else if (module.can.useAPI()) {
586
+ module.search.remote(searchTerm, callback);
587
+ } else {
588
+ module.error(error.source);
589
+ callback();
590
+ }
591
+ }
592
+ settings.onSearchQuery.call(element, searchTerm);
593
+ } else {
594
+ module.hideResults();
595
+ }
596
+ },
597
+
598
+ search: {
599
+ local: function (searchTerm) {
600
+ var
601
+ results = module.search.object(searchTerm, settings.source),
602
+ searchHTML
603
+ ;
604
+ module.set.loading();
605
+ module.save.results(results);
606
+ module.debug('Returned full local search results', results);
607
+ if (settings.maxResults > 0) {
608
+ module.debug('Using specified max results', results);
609
+ results = results.slice(0, settings.maxResults);
610
+ }
611
+ if (settings.type === 'category') {
612
+ results = module.create.categoryResults(results);
613
+ }
614
+ searchHTML = module.generateResults({
615
+ results: results,
616
+ });
617
+ module.remove.loading();
618
+ module.addResults(searchHTML);
619
+ module.inject.id(results);
620
+ module.write.cache(searchTerm, {
621
+ html: searchHTML,
622
+ results: results,
623
+ });
624
+ },
625
+ remote: function (searchTerm, callback) {
626
+ callback = isFunction(callback)
627
+ ? callback
628
+ : function () {};
629
+ if ($module.api('is loading')) {
630
+ $module.api('abort');
631
+ }
632
+ module.setup.api(searchTerm, callback);
633
+ $module
634
+ .api('query')
635
+ ;
636
+ },
637
+ object: function (searchTerm, source, searchFields) {
638
+ searchTerm = module.remove.diacritics(String(searchTerm));
639
+ var
640
+ results = [],
641
+ exactResults = [],
642
+ fuzzyResults = [],
643
+ searchExp = searchTerm.replace(regExp.escape, '\\$&'),
644
+ matchRegExp = new RegExp(regExp.beginsWith + searchExp, settings.ignoreSearchCase ? 'i' : ''),
645
+
646
+ // avoid duplicates when pushing results
647
+ addResult = function (array, result) {
648
+ var
649
+ notResult = $.inArray(result, results) === -1,
650
+ notFuzzyResult = $.inArray(result, fuzzyResults) === -1,
651
+ notExactResults = $.inArray(result, exactResults) === -1
652
+ ;
653
+ if (notResult && notFuzzyResult && notExactResults) {
654
+ array.push(result);
655
+ }
656
+ }
657
+ ;
658
+ source = source || settings.source;
659
+ searchFields = searchFields !== undefined
660
+ ? searchFields
661
+ : settings.searchFields;
662
+
663
+ // search fields should be array to loop correctly
664
+ if (!Array.isArray(searchFields)) {
665
+ searchFields = [searchFields];
666
+ }
667
+
668
+ // exit conditions if no source
669
+ if (source === undefined || source === false) {
670
+ module.error(error.source);
671
+
672
+ return [];
673
+ }
674
+ // iterate through search fields looking for matches
675
+ var lastSearchFieldIndex = searchFields.length - 1;
676
+ $.each(source, function (label, content) {
677
+ var concatenatedContent = [];
678
+ $.each(searchFields, function (index, field) {
679
+ var
680
+ fieldExists = typeof content[field] === 'string' || typeof content[field] === 'number'
681
+ ;
682
+ if (fieldExists) {
683
+ var text;
684
+ text = typeof content[field] === 'string'
685
+ ? module.remove.diacritics(content[field])
686
+ : content[field].toString();
687
+ text = $('<div/>', { html: text }).text().trim();
688
+ if (settings.fullTextSearch === 'all') {
689
+ concatenatedContent.push(text);
690
+ if (index < lastSearchFieldIndex) {
691
+ return true;
692
+ }
693
+ text = concatenatedContent.join(' ');
694
+ }
695
+ if (settings.fullTextSearch !== 'all' && text.search(matchRegExp) !== -1) {
696
+ // content starts with value (first in results)
697
+ addResult(results, content);
698
+ } else if (settings.fullTextSearch === 'exact' && module.exactSearch(searchTerm, text)) {
699
+ addResult(exactResults, content);
700
+ } else if (settings.fullTextSearch === 'some' && module.wordSearch(searchTerm, text)) {
701
+ addResult(exactResults, content);
702
+ } else if (settings.fullTextSearch === 'all' && module.wordSearch(searchTerm, text, true)) {
703
+ addResult(exactResults, content);
704
+ } else if (settings.fullTextSearch === true && module.fuzzySearch(searchTerm, text)) {
705
+ // content fuzzy matches (last in results)
706
+ addResult(fuzzyResults, content);
707
+ }
708
+ }
709
+ });
710
+ });
711
+ $.merge(exactResults, fuzzyResults);
712
+ $.merge(results, exactResults);
713
+
714
+ return results;
715
+ },
716
+ },
717
+ exactSearch: function (query, term) {
718
+ if (settings.ignoreSearchCase) {
719
+ query = query.toLowerCase();
720
+ term = term.toLowerCase();
721
+ }
722
+
723
+ return term.indexOf(query) > -1;
724
+ },
725
+ wordSearch: function (query, term, matchAll) {
726
+ var allWords = query.split(/\s+/),
727
+ w,
728
+ wL = allWords.length,
729
+ found = false
730
+ ;
731
+ for (w = 0; w < wL; w++) {
732
+ found = module.exactSearch(allWords[w], term);
733
+ if ((!found && matchAll) || (found && !matchAll)) {
734
+ break;
735
+ }
736
+ }
737
+
738
+ return found;
739
+ },
740
+ fuzzySearch: function (query, term) {
741
+ var
742
+ termLength = term.length,
743
+ queryLength = query.length
744
+ ;
745
+ if (typeof query !== 'string') {
746
+ return false;
747
+ }
748
+ if (settings.ignoreSearchCase) {
749
+ query = query.toLowerCase();
750
+ term = term.toLowerCase();
751
+ }
752
+ if (queryLength > termLength) {
753
+ return false;
754
+ }
755
+ if (queryLength === termLength) {
756
+ return query === term;
757
+ }
758
+ for (var characterIndex = 0, nextCharacterIndex = 0; characterIndex < queryLength; characterIndex++) {
759
+ var
760
+ continueSearch = false,
761
+ queryCharacter = query.charCodeAt(characterIndex)
762
+ ;
763
+ while (nextCharacterIndex < termLength) {
764
+ if (term.charCodeAt(nextCharacterIndex++) === queryCharacter) {
765
+ continueSearch = true;
766
+
767
+ break;
768
+ }
769
+ }
770
+
771
+ if (!continueSearch) {
772
+ return false;
773
+ }
774
+ }
775
+
776
+ return true;
777
+ },
778
+
779
+ parse: {
780
+ response: function (response, searchTerm) {
781
+ if (Array.isArray(response)) {
782
+ var o = {};
783
+ o[fields.results] = response;
784
+ response = o;
785
+ }
786
+ var
787
+ searchHTML = module.generateResults(response)
788
+ ;
789
+ module.verbose('Parsing server response', response);
790
+ if (response !== undefined) {
791
+ if (searchTerm !== undefined && response[fields.results] !== undefined) {
792
+ module.addResults(searchHTML);
793
+ module.inject.id(response[fields.results]);
794
+ module.write.cache(searchTerm, {
795
+ html: searchHTML,
796
+ results: response[fields.results],
797
+ });
798
+ module.save.results(response[fields.results]);
799
+ }
800
+ }
801
+ },
802
+ },
803
+
804
+ cancel: {
805
+ query: function () {
806
+ if (module.can.useAPI()) {
807
+ $module.api('abort');
808
+ }
809
+ },
810
+ },
811
+
812
+ has: {
813
+ minimumCharacters: function () {
814
+ var
815
+ searchTerm = module.get.value(),
816
+ numCharacters = searchTerm.length
817
+ ;
818
+
819
+ return numCharacters >= settings.minCharacters;
820
+ },
821
+ results: function () {
822
+ if ($results.length === 0) {
823
+ return false;
824
+ }
825
+ var
826
+ html = $results.html()
827
+ ;
828
+
829
+ return html !== '';
830
+ },
831
+ },
832
+
833
+ clear: {
834
+ cache: function (value) {
835
+ var
836
+ cache = $module.data(metadata.cache)
837
+ ;
838
+ if (!value) {
839
+ module.debug('Clearing cache', value);
840
+ $module.removeData(metadata.cache);
841
+ } else if (value && cache && cache[value]) {
842
+ module.debug('Removing value from cache', value);
843
+ delete cache[value];
844
+ $module.data(metadata.cache, cache);
845
+ }
846
+ },
847
+ value: function () {
848
+ module.set.value('');
849
+ },
850
+ },
851
+
852
+ read: {
853
+ cache: function (name) {
854
+ var
855
+ cache = $module.data(metadata.cache)
856
+ ;
857
+ if (settings.cache) {
858
+ module.verbose('Checking cache for generated html for query', name);
859
+
860
+ return (typeof cache === 'object') && (cache[name] !== undefined)
861
+ ? cache[name]
862
+ : false;
863
+ }
864
+
865
+ return false;
866
+ },
867
+ },
868
+
869
+ create: {
870
+ categoryResults: function (results) {
871
+ var
872
+ categoryResults = {}
873
+ ;
874
+ $.each(results, function (index, result) {
875
+ if (!result.category) {
876
+ return;
877
+ }
878
+ if (categoryResults[result.category] === undefined) {
879
+ module.verbose('Creating new category of results', result.category);
880
+ categoryResults[result.category] = {
881
+ name: result.category,
882
+ results: [result],
883
+ };
884
+ } else {
885
+ categoryResults[result.category].results.push(result);
886
+ }
887
+ });
888
+
889
+ return categoryResults;
890
+ },
891
+ id: function (resultIndex, categoryIndex) {
892
+ var
893
+ resultID = resultIndex + 1, // not zero indexed
894
+ letterID,
895
+ id
896
+ ;
897
+ if (categoryIndex !== undefined) {
898
+ // start char code for "A"
899
+ letterID = String.fromCharCode(97 + categoryIndex);
900
+ id = letterID + resultID;
901
+ module.verbose('Creating category result id', id);
902
+ } else {
903
+ id = resultID;
904
+ module.verbose('Creating result id', id);
905
+ }
906
+
907
+ return id;
908
+ },
909
+ results: function () {
910
+ if ($results.length === 0) {
911
+ $results = $('<div />')
912
+ .addClass(className.results)
913
+ .appendTo($module)
914
+ ;
915
+ }
916
+ },
917
+ },
918
+
919
+ inject: {
920
+ result: function (result, resultIndex, categoryIndex) {
921
+ module.verbose('Injecting result into results');
922
+ var
923
+ $selectedResult = categoryIndex !== undefined
924
+ ? $results
925
+ .children().eq(categoryIndex)
926
+ .children(selector.results)
927
+ .first()
928
+ .children(selector.result)
929
+ .eq(resultIndex)
930
+ : $results
931
+ .children(selector.result).eq(resultIndex)
932
+ ;
933
+ module.verbose('Injecting results metadata', $selectedResult);
934
+ $selectedResult
935
+ .data(metadata.result, result)
936
+ ;
937
+ },
938
+ id: function (results) {
939
+ module.debug('Injecting unique ids into results');
940
+ var
941
+ // since results may be object, we must use counters
942
+ categoryIndex = 0,
943
+ resultIndex = 0
944
+ ;
945
+ if (settings.type === 'category') {
946
+ // iterate through each category result
947
+ $.each(results, function (index, category) {
948
+ if (category.results.length > 0) {
949
+ resultIndex = 0;
950
+ $.each(category.results, function (index, result) {
951
+ if (result.id === undefined) {
952
+ result.id = module.create.id(resultIndex, categoryIndex);
953
+ }
954
+ module.inject.result(result, resultIndex, categoryIndex);
955
+ resultIndex++;
956
+ });
957
+ categoryIndex++;
958
+ }
959
+ });
960
+ } else {
961
+ // top level
962
+ $.each(results, function (index, result) {
963
+ if (result.id === undefined) {
964
+ result.id = module.create.id(resultIndex);
965
+ }
966
+ module.inject.result(result, resultIndex);
967
+ resultIndex++;
968
+ });
969
+ }
970
+
971
+ return results;
972
+ },
973
+ },
974
+
975
+ save: {
976
+ results: function (results) {
977
+ module.verbose('Saving current search results to metadata', results);
978
+ $module.data(metadata.results, results);
979
+ },
980
+ },
981
+
982
+ write: {
983
+ cache: function (name, value) {
984
+ var
985
+ cache = $module.data(metadata.cache) !== undefined
986
+ ? $module.data(metadata.cache)
987
+ : {}
988
+ ;
989
+ if (settings.cache) {
990
+ module.verbose('Writing generated html to cache', name, value);
991
+ cache[name] = value;
992
+ $module
993
+ .data(metadata.cache, cache)
994
+ ;
995
+ }
996
+ },
997
+ },
998
+
999
+ addResults: function (html) {
1000
+ if (isFunction(settings.onResultsAdd)) {
1001
+ if (settings.onResultsAdd.call($results, html) === false) {
1002
+ module.debug('onResultsAdd callback cancelled default action');
1003
+
1004
+ return false;
1005
+ }
1006
+ }
1007
+ if (html) {
1008
+ $results
1009
+ .html(html)
1010
+ ;
1011
+ module.refreshResults();
1012
+ if (settings.selectFirstResult) {
1013
+ module.select.firstResult();
1014
+ }
1015
+ module.showResults();
1016
+ } else {
1017
+ module.hideResults(function () {
1018
+ $results.empty();
1019
+ });
1020
+ }
1021
+ },
1022
+
1023
+ showResults: function (callback) {
1024
+ callback = isFunction(callback)
1025
+ ? callback
1026
+ : function () {};
1027
+ if (resultsDismissed) {
1028
+ return;
1029
+ }
1030
+ if (!module.is.visible() && module.has.results()) {
1031
+ if (module.can.transition()) {
1032
+ module.debug('Showing results with css animations');
1033
+ $results
1034
+ .transition({
1035
+ animation: settings.transition + ' in',
1036
+ debug: settings.debug,
1037
+ verbose: settings.verbose,
1038
+ silent: settings.silent,
1039
+ duration: settings.duration,
1040
+ onShow: function () {
1041
+ var $firstResult = $module.find(selector.result).eq(0);
1042
+ module.ensureVisible($firstResult);
1043
+ },
1044
+ onComplete: function () {
1045
+ callback();
1046
+ },
1047
+ queue: true,
1048
+ })
1049
+ ;
1050
+ } else {
1051
+ module.debug('Showing results with javascript');
1052
+ $results
1053
+ .stop()
1054
+ .fadeIn(settings.duration, settings.easing)
1055
+ ;
1056
+ }
1057
+ settings.onResultsOpen.call($results);
1058
+ }
1059
+ },
1060
+ hideResults: function (callback) {
1061
+ callback = isFunction(callback)
1062
+ ? callback
1063
+ : function () {};
1064
+ if (module.is.visible()) {
1065
+ if (module.can.transition()) {
1066
+ module.debug('Hiding results with css animations');
1067
+ $results
1068
+ .transition({
1069
+ animation: settings.transition + ' out',
1070
+ debug: settings.debug,
1071
+ verbose: settings.verbose,
1072
+ silent: settings.silent,
1073
+ duration: settings.duration,
1074
+ onComplete: function () {
1075
+ callback();
1076
+ },
1077
+ queue: true,
1078
+ })
1079
+ ;
1080
+ } else {
1081
+ module.debug('Hiding results with javascript');
1082
+ $results
1083
+ .stop()
1084
+ .fadeOut(settings.duration, settings.easing)
1085
+ ;
1086
+ }
1087
+ settings.onResultsClose.call($results);
1088
+ }
1089
+ },
1090
+
1091
+ generateResults: function (response) {
1092
+ module.debug('Generating html from response', response);
1093
+ var
1094
+ template = settings.templates[settings.type],
1095
+ isProperObject = $.isPlainObject(response[fields.results]) && !$.isEmptyObject(response[fields.results]),
1096
+ isProperArray = Array.isArray(response[fields.results]) && response[fields.results].length > 0,
1097
+ html = ''
1098
+ ;
1099
+ if (isProperObject || isProperArray) {
1100
+ if (settings.maxResults > 0) {
1101
+ if (isProperObject) {
1102
+ if (settings.type === 'standard') {
1103
+ module.error(error.maxResults);
1104
+ }
1105
+ } else {
1106
+ response[fields.results] = response[fields.results].slice(0, settings.maxResults);
1107
+ }
1108
+ }
1109
+ if (settings.highlightMatches) {
1110
+ var results = response[fields.results],
1111
+ regExpIgnore = settings.ignoreSearchCase ? 'i' : '',
1112
+ querySplit = module.get.value().split(''),
1113
+ diacriticReg = settings.ignoreDiacritics ? '[\u0300-\u036F]?' : '',
1114
+ htmlReg = '(?![^<]*>)',
1115
+ markedRegExp = new RegExp(htmlReg + '(' + querySplit.join(diacriticReg + ')(.*?)' + htmlReg + '(') + diacriticReg + ')', regExpIgnore),
1116
+ markedReplacer = function () {
1117
+ var args = [].slice.call(arguments, 1, querySplit.length * 2).map(function (x, i) {
1118
+ return i & 1 ? x : '<mark>' + x + '</mark>'; // eslint-disable-line no-bitwise
1119
+ });
1120
+
1121
+ return args.join('');
1122
+ }
1123
+ ;
1124
+ $.each(results, function (label, content) {
1125
+ $.each(settings.searchFields, function (index, field) {
1126
+ var
1127
+ fieldExists = typeof content[field] === 'string' || typeof content[field] === 'number'
1128
+ ;
1129
+ if (fieldExists) {
1130
+ var markedHTML = typeof content[field] === 'string'
1131
+ ? content[field]
1132
+ : content[field].toString();
1133
+ if (settings.ignoreDiacritics) {
1134
+ markedHTML = markedHTML.normalize('NFD');
1135
+ }
1136
+ markedHTML = markedHTML.replace(/<\/?mark>/g, '');
1137
+ response[fields.results][label][field] = markedHTML.replace(markedRegExp, markedReplacer);
1138
+ }
1139
+ });
1140
+ });
1141
+ }
1142
+ if (isFunction(template)) {
1143
+ html = template(response, fields, settings.preserveHTML);
1144
+ } else {
1145
+ module.error(error.noTemplate, false);
1146
+ }
1147
+ } else if (settings.showNoResults) {
1148
+ html = module.displayMessage(error.noResults, 'empty', error.noResultsHeader);
1149
+ }
1150
+ settings.onResults.call(element, response);
1151
+
1152
+ return html;
1153
+ },
1154
+
1155
+ displayMessage: function (text, type, header) {
1156
+ type = type || 'standard';
1157
+ module.debug('Displaying message', text, type, header);
1158
+ module.addResults(settings.templates.message(text, type, header));
1159
+
1160
+ return settings.templates.message(text, type, header);
1161
+ },
1162
+
1163
+ setting: function (name, value) {
1164
+ if ($.isPlainObject(name)) {
1165
+ $.extend(true, settings, name);
1166
+ } else if (value !== undefined) {
1167
+ settings[name] = value;
1168
+ } else {
1169
+ return settings[name];
1170
+ }
1171
+ },
1172
+ internal: function (name, value) {
1173
+ if ($.isPlainObject(name)) {
1174
+ $.extend(true, module, name);
1175
+ } else if (value !== undefined) {
1176
+ module[name] = value;
1177
+ } else {
1178
+ return module[name];
1179
+ }
1180
+ },
1181
+ debug: function () {
1182
+ if (!settings.silent && settings.debug) {
1183
+ if (settings.performance) {
1184
+ module.performance.log(arguments);
1185
+ } else {
1186
+ module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
1187
+ module.debug.apply(console, arguments);
1188
+ }
1189
+ }
1190
+ },
1191
+ verbose: function () {
1192
+ if (!settings.silent && settings.verbose && settings.debug) {
1193
+ if (settings.performance) {
1194
+ module.performance.log(arguments);
1195
+ } else {
1196
+ module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
1197
+ module.verbose.apply(console, arguments);
1198
+ }
1199
+ }
1200
+ },
1201
+ error: function () {
1202
+ if (!settings.silent) {
1203
+ module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
1204
+ module.error.apply(console, arguments);
1205
+ }
1206
+ },
1207
+ performance: {
1208
+ log: function (message) {
1209
+ var
1210
+ currentTime,
1211
+ executionTime,
1212
+ previousTime
1213
+ ;
1214
+ if (settings.performance) {
1215
+ currentTime = Date.now();
1216
+ previousTime = time || currentTime;
1217
+ executionTime = currentTime - previousTime;
1218
+ time = currentTime;
1219
+ performance.push({
1220
+ Name: message[0],
1221
+ Arguments: [].slice.call(message, 1) || '',
1222
+ Element: element,
1223
+ 'Execution Time': executionTime,
1224
+ });
1225
+ }
1226
+ clearTimeout(module.performance.timer);
1227
+ module.performance.timer = setTimeout(function () {
1228
+ module.performance.display();
1229
+ }, 500);
1230
+ },
1231
+ display: function () {
1232
+ var
1233
+ title = settings.name + ':',
1234
+ totalTime = 0
1235
+ ;
1236
+ time = false;
1237
+ clearTimeout(module.performance.timer);
1238
+ $.each(performance, function (index, data) {
1239
+ totalTime += data['Execution Time'];
1240
+ });
1241
+ title += ' ' + totalTime + 'ms';
1242
+ if ($allModules.length > 1) {
1243
+ title += ' (' + $allModules.length + ')';
1244
+ }
1245
+ if (performance.length > 0) {
1246
+ console.groupCollapsed(title);
1247
+ if (console.table) {
1248
+ console.table(performance);
1249
+ } else {
1250
+ $.each(performance, function (index, data) {
1251
+ console.log(data.Name + ': ' + data['Execution Time'] + 'ms');
1252
+ });
1253
+ }
1254
+ console.groupEnd();
1255
+ }
1256
+ performance = [];
1257
+ },
1258
+ },
1259
+ invoke: function (query, passedArguments, context) {
1260
+ var
1261
+ object = instance,
1262
+ maxDepth,
1263
+ found,
1264
+ response
1265
+ ;
1266
+ passedArguments = passedArguments || queryArguments;
1267
+ context = context || element;
1268
+ if (typeof query === 'string' && object !== undefined) {
1269
+ query = query.split(/[ .]/);
1270
+ maxDepth = query.length - 1;
1271
+ $.each(query, function (depth, value) {
1272
+ var camelCaseValue = depth !== maxDepth
1273
+ ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
1274
+ : query
1275
+ ;
1276
+ if ($.isPlainObject(object[camelCaseValue]) && (depth !== maxDepth)) {
1277
+ object = object[camelCaseValue];
1278
+ } else if (object[camelCaseValue] !== undefined) {
1279
+ found = object[camelCaseValue];
1280
+
1281
+ return false;
1282
+ } else if ($.isPlainObject(object[value]) && (depth !== maxDepth)) {
1283
+ object = object[value];
1284
+ } else if (object[value] !== undefined) {
1285
+ found = object[value];
1286
+
1287
+ return false;
1288
+ } else {
1289
+ module.error(error.method, query);
1290
+
1291
+ return false;
1292
+ }
1293
+ });
1294
+ }
1295
+ if (isFunction(found)) {
1296
+ response = found.apply(context, passedArguments);
1297
+ } else if (found !== undefined) {
1298
+ response = found;
1299
+ }
1300
+ if (Array.isArray(returnedValue)) {
1301
+ returnedValue.push(response);
1302
+ } else if (returnedValue !== undefined) {
1303
+ returnedValue = [returnedValue, response];
1304
+ } else if (response !== undefined) {
1305
+ returnedValue = response;
1306
+ }
1307
+
1308
+ return found;
1309
+ },
1310
+ };
1311
+ if (methodInvoked) {
1312
+ if (instance === undefined) {
1313
+ module.initialize();
1314
+ }
1315
+ module.invoke(query);
1316
+ } else {
1317
+ if (instance !== undefined) {
1318
+ instance.invoke('destroy');
1319
+ }
1320
+ module.initialize();
1321
+ }
1322
+ });
1323
+
1324
+ return returnedValue !== undefined
1325
+ ? returnedValue
1326
+ : this;
1327
+ };
1328
+
1329
+ $.fn.search.settings = {
1330
+
1331
+ name: 'Search',
1332
+ namespace: 'search',
1333
+
1334
+ silent: false,
1335
+ debug: false,
1336
+ verbose: false,
1337
+ performance: true,
1338
+
1339
+ // template to use (specified in settings.templates)
1340
+ type: 'standard',
1341
+
1342
+ // minimum characters required to search
1343
+ minCharacters: 1,
1344
+
1345
+ // whether to select first result after searching automatically
1346
+ selectFirstResult: false,
1347
+
1348
+ // API config
1349
+ apiSettings: false,
1350
+
1351
+ // object to search
1352
+ source: false,
1353
+
1354
+ // Whether search should query current term on focus
1355
+ searchOnFocus: true,
1356
+
1357
+ // fields to search
1358
+ searchFields: [
1359
+ 'id',
1360
+ 'title',
1361
+ 'description',
1362
+ ],
1363
+
1364
+ // field to display in standard results template
1365
+ displayField: '',
1366
+
1367
+ // search anywhere in value (set to 'exact' to require exact matches
1368
+ fullTextSearch: 'exact',
1369
+
1370
+ // Whether search result should highlight matching strings
1371
+ highlightMatches: false,
1372
+
1373
+ // match results also if they contain diacritics of the same base character (for example searching for "a" will also match "á" or "â" or "à", etc...)
1374
+ ignoreDiacritics: false,
1375
+
1376
+ // whether to consider case sensitivity on local searching
1377
+ ignoreSearchCase: true,
1378
+
1379
+ // whether to add events to prompt automatically
1380
+ automatic: true,
1381
+
1382
+ // delay before hiding menu after blur
1383
+ hideDelay: 0,
1384
+
1385
+ // delay before searching
1386
+ searchDelay: 200,
1387
+
1388
+ // maximum results returned from search
1389
+ maxResults: 7,
1390
+
1391
+ // whether to store lookups in local cache
1392
+ cache: true,
1393
+
1394
+ // whether no results errors should be shown
1395
+ showNoResults: true,
1396
+
1397
+ // preserve possible html of resultset values
1398
+ preserveHTML: true,
1399
+
1400
+ // transition settings
1401
+ transition: 'scale',
1402
+ duration: 200,
1403
+ easing: 'easeOutExpo',
1404
+
1405
+ // callbacks
1406
+ onSelect: false,
1407
+ onResultsAdd: false,
1408
+
1409
+ onSearchQuery: function (query) {},
1410
+ onResults: function (response, fromCache) {},
1411
+
1412
+ onResultsOpen: function () {},
1413
+ onResultsClose: function () {},
1414
+
1415
+ className: {
1416
+ animating: 'animating',
1417
+ active: 'active',
1418
+ category: 'category',
1419
+ empty: 'empty',
1420
+ focus: 'focus',
1421
+ hidden: 'hidden',
1422
+ loading: 'loading',
1423
+ results: 'results',
1424
+ pressed: 'down',
1425
+ },
1426
+
1427
+ error: {
1428
+ source: 'Cannot search. No source used, and Fomantic API module was not included',
1429
+ noResultsHeader: 'No Results',
1430
+ noResults: 'Your search returned no results',
1431
+ noTemplate: 'A valid template name was not specified.',
1432
+ oldSearchSyntax: 'searchFullText setting has been renamed fullTextSearch for consistency, please adjust your settings.',
1433
+ serverError: 'There was an issue querying the server.',
1434
+ maxResults: 'Results must be an array to use maxResults setting',
1435
+ method: 'The method you called is not defined.',
1436
+ noNormalize: '"ignoreDiacritics" setting will be ignored. Browser does not support String().normalize(). You may consider including <https://cdn.jsdelivr.net/npm/unorm@1.4.1/lib/unorm.min.js> as a polyfill.',
1437
+ },
1438
+
1439
+ metadata: {
1440
+ cache: 'cache',
1441
+ results: 'results',
1442
+ result: 'result',
1443
+ },
1444
+
1445
+ regExp: {
1446
+ escape: /[$()*+./?[\\\]^{|}-]/g,
1447
+ beginsWith: '(?:\\s|^)',
1448
+ },
1449
+
1450
+ // maps api response attributes to internal representation
1451
+ fields: {
1452
+ categories: 'results', // array of categories (category view)
1453
+ categoryName: 'name', // name of category (category view)
1454
+ categoryResults: 'results', // array of results (category view)
1455
+ description: 'description', // result description
1456
+ image: 'image', // result image
1457
+ alt: 'alt', // result alt text for image
1458
+ price: 'price', // result price
1459
+ results: 'results', // array of results (standard)
1460
+ title: 'title', // result title
1461
+ url: 'url', // result url
1462
+ action: 'action', // "view more" object name
1463
+ actionText: 'text', // "view more" text
1464
+ actionURL: 'url', // "view more" url
1465
+ },
1466
+
1467
+ selector: {
1468
+ prompt: '.prompt',
1469
+ remove: '> .icon.input > .remove.icon',
1470
+ searchButton: '.search.button',
1471
+ results: '.results',
1472
+ message: '.results > .message',
1473
+ category: '.category',
1474
+ result: '.result',
1475
+ title: '.title, .name',
1476
+ },
1477
+
1478
+ templates: {
1479
+ escape: function (string, preserveHTML) {
1480
+ if (preserveHTML) {
1481
+ return string;
1482
+ }
1483
+ var
1484
+ badChars = /["'<>`]/g,
1485
+ shouldEscape = /["&'<>`]/,
1486
+ escape = {
1487
+ '<': '&lt;',
1488
+ '>': '&gt;',
1489
+ '"': '&quot;',
1490
+ "'": '&#x27;',
1491
+ '`': '&#x60;',
1492
+ },
1493
+ escapedChar = function (chr) {
1494
+ return escape[chr];
1495
+ };
1496
+ if (shouldEscape.test(string)) {
1497
+ string = string.replace(/&(?![\d#a-z]{1,12};)/gi, '&amp;');
1498
+ string = string.replace(badChars, escapedChar);
1499
+ // FUI controlled HTML is still allowed
1500
+ string = string.replace(/&lt;(\/)*mark&gt;/g, '<$1mark>');
1501
+ }
1502
+
1503
+ return string;
1504
+ },
1505
+ message: function (message, type, header) {
1506
+ var
1507
+ html = ''
1508
+ ;
1509
+ if (message !== undefined && type !== undefined) {
1510
+ html += ''
1511
+ + '<div class="message ' + type + '">';
1512
+ if (header) {
1513
+ html += ''
1514
+ + '<div class="header">' + header + '</div>';
1515
+ }
1516
+ html += ' <div class="description">' + message + '</div>';
1517
+ html += '</div>';
1518
+ }
1519
+
1520
+ return html;
1521
+ },
1522
+ category: function (response, fields, preserveHTML) {
1523
+ var
1524
+ html = '',
1525
+ escape = $.fn.search.settings.templates.escape
1526
+ ;
1527
+ if (response[fields.categoryResults] !== undefined) {
1528
+ // each category
1529
+ $.each(response[fields.categoryResults], function (index, category) {
1530
+ if (category[fields.results] !== undefined && category.results.length > 0) {
1531
+ html += '<div class="category">';
1532
+
1533
+ if (category[fields.categoryName] !== undefined) {
1534
+ html += '<div class="name">' + escape(category[fields.categoryName], preserveHTML) + '</div>';
1535
+ }
1536
+
1537
+ // each item inside category
1538
+ html += '<div class="results">';
1539
+ $.each(category.results, function (index, result) {
1540
+ html += result[fields.url]
1541
+ ? '<a class="result" href="' + result[fields.url].replace(/"/g, '') + '">'
1542
+ : '<a class="result">';
1543
+ if (result[fields.image] !== undefined) {
1544
+ html += ''
1545
+ + '<div class="image">'
1546
+ + ' <img src="' + result[fields.image].replace(/"/g, '') + (result[fields.alt] ? '" alt="' + result[fields.alt].replace(/"/g, '') : '') + '">'
1547
+ + '</div>';
1548
+ }
1549
+ html += '<div class="content">';
1550
+ if (result[fields.price] !== undefined) {
1551
+ html += '<div class="price">' + escape(result[fields.price], preserveHTML) + '</div>';
1552
+ }
1553
+ if (result[fields.title] !== undefined) {
1554
+ html += '<div class="title">' + escape(result[fields.title], preserveHTML) + '</div>';
1555
+ }
1556
+ if (result[fields.description] !== undefined) {
1557
+ html += '<div class="description">' + escape(result[fields.description], preserveHTML) + '</div>';
1558
+ }
1559
+ html += ''
1560
+ + '</div>';
1561
+ html += '</a>';
1562
+ });
1563
+ html += '</div>';
1564
+ html += ''
1565
+ + '</div>';
1566
+ }
1567
+ });
1568
+ if (response[fields.action]) {
1569
+ html += fields.actionURL === false
1570
+ ? ''
1571
+ + '<div class="action">'
1572
+ + escape(response[fields.action][fields.actionText], preserveHTML)
1573
+ + '</div>'
1574
+ : ''
1575
+ + '<a href="' + response[fields.action][fields.actionURL].replace(/"/g, '') + '" class="action">'
1576
+ + escape(response[fields.action][fields.actionText], preserveHTML)
1577
+ + '</a>';
1578
+ }
1579
+
1580
+ return html;
1581
+ }
1582
+
1583
+ return false;
1584
+ },
1585
+ standard: function (response, fields, preserveHTML) {
1586
+ var
1587
+ html = '',
1588
+ escape = $.fn.search.settings.templates.escape
1589
+ ;
1590
+ if (response[fields.results] !== undefined) {
1591
+ // each result
1592
+ $.each(response[fields.results], function (index, result) {
1593
+ html += result[fields.url]
1594
+ ? '<a class="result" href="' + result[fields.url].replace(/"/g, '') + '">'
1595
+ : '<a class="result">';
1596
+ if (result[fields.image] !== undefined) {
1597
+ html += ''
1598
+ + '<div class="image">'
1599
+ + ' <img src="' + result[fields.image].replace(/"/g, '') + (result[fields.alt] ? '" alt="' + result[fields.alt].replace(/"/g, '') : '') + '">'
1600
+ + '</div>';
1601
+ }
1602
+ html += '<div class="content">';
1603
+ if (result[fields.price] !== undefined) {
1604
+ html += '<div class="price">' + escape(result[fields.price], preserveHTML) + '</div>';
1605
+ }
1606
+ if (result[fields.title] !== undefined) {
1607
+ html += '<div class="title">' + escape(result[fields.title], preserveHTML) + '</div>';
1608
+ }
1609
+ if (result[fields.description] !== undefined) {
1610
+ html += '<div class="description">' + escape(result[fields.description], preserveHTML) + '</div>';
1611
+ }
1612
+ html += ''
1613
+ + '</div>';
1614
+ html += '</a>';
1615
+ });
1616
+ if (response[fields.action]) {
1617
+ html += fields.actionURL === false
1618
+ ? ''
1619
+ + '<div class="action">'
1620
+ + escape(response[fields.action][fields.actionText], preserveHTML)
1621
+ + '</div>'
1622
+ : ''
1623
+ + '<a href="' + response[fields.action][fields.actionURL].replace(/"/g, '') + '" class="action">'
1624
+ + escape(response[fields.action][fields.actionText], preserveHTML)
1625
+ + '</a>';
1626
+ }
1627
+
1628
+ return html;
1629
+ }
1630
+
1631
+ return false;
1632
+ },
1633
+ },
1634
+ };
1635
+
1636
+ $.extend($.easing, {
1637
+ easeOutExpo: function (x) {
1638
+ return x === 1 ? 1 : 1 - Math.pow(2, -10 * x);
1639
+ },
1640
+ });
1641
+ })(jQuery, window, document);