fustrate-rails 0.4.1 → 0.10.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 (61) hide show
  1. checksums.yaml +5 -5
  2. data/config/initializers/jbuilder.rb +13 -0
  3. data/config/initializers/renderers.rb +40 -0
  4. data/config/initializers/sanitize.rb +112 -0
  5. data/config/rubocop/default.yml +96 -0
  6. data/config/rubocop/rails.yml +22 -0
  7. data/lib/fustrate/rails/concerns/clean_attributes.rb +49 -0
  8. data/lib/fustrate/rails/concerns/model.rb +48 -0
  9. data/lib/fustrate/rails/concerns/sanitize_html.rb +34 -0
  10. data/lib/fustrate/rails/engine.rb +14 -7
  11. data/lib/fustrate/rails/services/base.rb +46 -0
  12. data/lib/fustrate/rails/services/generate_csv.rb +35 -0
  13. data/lib/fustrate/rails/services/generate_excel.rb +32 -0
  14. data/lib/fustrate/rails/services/log_edit.rb +132 -0
  15. data/lib/fustrate/rails/spec_helper.rb +62 -0
  16. data/lib/fustrate/rails/version.rb +5 -1
  17. data/lib/fustrate-rails.rb +5 -2
  18. metadata +44 -140
  19. data/vendor/assets/javascripts/awesomplete.js +0 -402
  20. data/vendor/assets/javascripts/fustrate/_module.coffee +0 -140
  21. data/vendor/assets/javascripts/fustrate/components/_module.coffee +0 -3
  22. data/vendor/assets/javascripts/fustrate/components/alert_box.coffee +0 -10
  23. data/vendor/assets/javascripts/fustrate/components/autocomplete.coffee +0 -161
  24. data/vendor/assets/javascripts/fustrate/components/disclosure.coffee +0 -12
  25. data/vendor/assets/javascripts/fustrate/components/drop_zone.coffee +0 -9
  26. data/vendor/assets/javascripts/fustrate/components/dropdown.coffee +0 -48
  27. data/vendor/assets/javascripts/fustrate/components/file_picker.coffee +0 -11
  28. data/vendor/assets/javascripts/fustrate/components/flash.coffee +0 -31
  29. data/vendor/assets/javascripts/fustrate/components/modal.coffee +0 -273
  30. data/vendor/assets/javascripts/fustrate/components/pagination.coffee +0 -84
  31. data/vendor/assets/javascripts/fustrate/components/tabs.coffee +0 -28
  32. data/vendor/assets/javascripts/fustrate/components/tooltip.coffee +0 -72
  33. data/vendor/assets/javascripts/fustrate/generic_form.coffee +0 -30
  34. data/vendor/assets/javascripts/fustrate/generic_page.coffee +0 -40
  35. data/vendor/assets/javascripts/fustrate/generic_table.coffee +0 -57
  36. data/vendor/assets/javascripts/fustrate/listenable.coffee +0 -25
  37. data/vendor/assets/javascripts/fustrate/object.coffee +0 -21
  38. data/vendor/assets/javascripts/fustrate/record.coffee +0 -23
  39. data/vendor/assets/javascripts/fustrate.coffee +0 -6
  40. data/vendor/assets/stylesheets/_fustrate.sass +0 -7
  41. data/vendor/assets/stylesheets/awesomplete.sass +0 -76
  42. data/vendor/assets/stylesheets/fustrate/_colors.sass +0 -12
  43. data/vendor/assets/stylesheets/fustrate/_settings.sass +0 -20
  44. data/vendor/assets/stylesheets/fustrate/components/_components.sass +0 -36
  45. data/vendor/assets/stylesheets/fustrate/components/_functions.sass +0 -41
  46. data/vendor/assets/stylesheets/fustrate/components/alerts.sass +0 -86
  47. data/vendor/assets/stylesheets/fustrate/components/buttons.sass +0 -99
  48. data/vendor/assets/stylesheets/fustrate/components/disclosures.sass +0 -23
  49. data/vendor/assets/stylesheets/fustrate/components/dropdowns.sass +0 -36
  50. data/vendor/assets/stylesheets/fustrate/components/flash.sass +0 -38
  51. data/vendor/assets/stylesheets/fustrate/components/forms.sass +0 -195
  52. data/vendor/assets/stylesheets/fustrate/components/grid.sass +0 -196
  53. data/vendor/assets/stylesheets/fustrate/components/labels.sass +0 -64
  54. data/vendor/assets/stylesheets/fustrate/components/modals.sass +0 -167
  55. data/vendor/assets/stylesheets/fustrate/components/pagination.sass +0 -69
  56. data/vendor/assets/stylesheets/fustrate/components/panels.sass +0 -67
  57. data/vendor/assets/stylesheets/fustrate/components/popovers.sass +0 -19
  58. data/vendor/assets/stylesheets/fustrate/components/tables.sass +0 -62
  59. data/vendor/assets/stylesheets/fustrate/components/tabs.sass +0 -44
  60. data/vendor/assets/stylesheets/fustrate/components/tooltips.sass +0 -28
  61. data/vendor/assets/stylesheets/fustrate/components/typography.sass +0 -391
@@ -1,402 +0,0 @@
1
- /* 1795543d988d0fd9ca6237a5ac176f8e88d63990 */
2
-
3
- /**
4
- * Simple, lightweight, usable local autocomplete library for modern browsers
5
- * Because there weren’t enough autocomplete scripts in the world? Because I’m completely insane and have NIH syndrome? Probably both. :P
6
- * @author Lea Verou http://leaverou.github.io/awesomplete
7
- * MIT license
8
- */
9
-
10
- (function () {
11
-
12
- var _ = function (input, o) {
13
- var me = this;
14
-
15
- // Setup
16
-
17
- this.input = $(input);
18
- this.input.setAttribute("autocomplete", "off");
19
- this.input.setAttribute("aria-autocomplete", "list");
20
-
21
- o = o || {};
22
-
23
- configure(this, {
24
- minChars: 2,
25
- maxItems: 10,
26
- autoFirst: false,
27
- filter: _.FILTER_CONTAINS,
28
- sort: _.SORT_BYLENGTH,
29
- item: _.ITEM,
30
- replace: _.REPLACE
31
- }, o);
32
-
33
- this.index = -1;
34
-
35
- // Create necessary elements
36
-
37
- this.container = $.create("div", {
38
- className: "awesomplete",
39
- around: input
40
- });
41
-
42
- this.ul = $.create("ul", {
43
- hidden: "hidden",
44
- inside: this.container
45
- });
46
-
47
- this.status = $.create("span", {
48
- className: "visually-hidden",
49
- role: "status",
50
- "aria-live": "assertive",
51
- "aria-relevant": "additions",
52
- inside: this.container
53
- });
54
-
55
- // Bind events
56
-
57
- $.bind(this.input, {
58
- "input": this.evaluate.bind(this),
59
- "blur": this.close.bind(this),
60
- "keydown": function(evt) {
61
- var c = evt.keyCode;
62
-
63
- // If the dropdown `ul` is in view, then act on keydown for the following keys:
64
- // Enter / Esc / Up / Down
65
- if(me.opened) {
66
- if (c === 13 && me.selected) { // Enter
67
- evt.preventDefault();
68
- me.select();
69
- }
70
- else if (c === 27) { // Esc
71
- me.close();
72
- }
73
- else if (c === 38 || c === 40) { // Down/Up arrow
74
- evt.preventDefault();
75
- me[c === 38? "previous" : "next"]();
76
- }
77
- }
78
- }
79
- });
80
-
81
- $.bind(this.input.form, {"submit": this.close.bind(this)});
82
-
83
- $.bind(this.ul, {"mousedown": function(evt) {
84
- var li = evt.target;
85
-
86
- if (li !== this) {
87
-
88
- while (li && !/li/i.test(li.nodeName)) {
89
- li = li.parentNode;
90
- }
91
-
92
- if (li && evt.button === 0) { // Only select on left click
93
- evt.preventDefault();
94
- me.select(li, evt.target);
95
- }
96
- }
97
- }});
98
-
99
- if (this.input.hasAttribute("list")) {
100
- this.list = "#" + this.input.getAttribute("list");
101
- this.input.removeAttribute("list");
102
- }
103
- else {
104
- this.list = this.input.getAttribute("data-list") || o.list || [];
105
- }
106
-
107
- _.all.push(this);
108
- };
109
-
110
- _.prototype = {
111
- set list(list) {
112
- if (Array.isArray(list)) {
113
- this._list = list;
114
- }
115
- else if (typeof list === "string" && list.indexOf(",") > -1) {
116
- this._list = list.split(/\s*,\s*/);
117
- }
118
- else { // Element or CSS selector
119
- list = $(list);
120
-
121
- if (list && list.children) {
122
- this._list = slice.apply(list.children).map(function (el) {
123
- return el.textContent.trim();
124
- });
125
- }
126
- }
127
-
128
- if (document.activeElement === this.input) {
129
- this.evaluate();
130
- }
131
- },
132
-
133
- get selected() {
134
- return this.index > -1;
135
- },
136
-
137
- get opened() {
138
- return !this.ul.hasAttribute("hidden");
139
- },
140
-
141
- close: function () {
142
- this.ul.setAttribute("hidden", "");
143
- this.index = -1;
144
-
145
- $.fire(this.input, "awesomplete-close");
146
- },
147
-
148
- open: function () {
149
- this.ul.removeAttribute("hidden");
150
-
151
- if (this.autoFirst && this.index === -1) {
152
- this.goto(0);
153
- }
154
-
155
- $.fire(this.input, "awesomplete-open");
156
- },
157
-
158
- next: function () {
159
- var count = this.ul.children.length;
160
-
161
- this.goto(this.index < count - 1? this.index + 1 : -1);
162
- },
163
-
164
- previous: function () {
165
- var count = this.ul.children.length;
166
-
167
- this.goto(this.selected? this.index - 1 : count - 1);
168
- },
169
-
170
- // Should not be used, highlights specific item without any checks!
171
- goto: function (i) {
172
- var lis = this.ul.children;
173
-
174
- if (this.selected) {
175
- lis[this.index].setAttribute("aria-selected", "false");
176
- }
177
-
178
- this.index = i;
179
-
180
- if (i > -1 && lis.length > 0) {
181
- lis[i].setAttribute("aria-selected", "true");
182
- this.status.textContent = lis[i].textContent;
183
- }
184
-
185
- $.fire(this.input, "awesomplete-highlight");
186
- },
187
-
188
- select: function (selected, origin) {
189
- selected = selected || this.ul.children[this.index];
190
-
191
- if (selected) {
192
- var allowed = $.fire(this.input, "awesomplete-select", {
193
- text: selected.textContent,
194
- data: this.suggestions[$.siblingIndex(selected)],
195
- origin: origin || selected
196
- });
197
-
198
- if (allowed) {
199
- this.replace(selected.textContent);
200
- this.close();
201
- $.fire(this.input, "awesomplete-selectcomplete");
202
- }
203
- }
204
- },
205
-
206
- evaluate: function() {
207
- var me = this;
208
- var value = this.input.value;
209
-
210
- if (value.length >= this.minChars && this._list.length > 0) {
211
- this.index = -1;
212
- // Populate list with options that match
213
- this.ul.innerHTML = "";
214
-
215
- this.suggestions = this._list
216
- .filter(function(item) {
217
- return me.filter(item, value);
218
- })
219
- .sort(this.sort)
220
- .slice(0, this.maxItems);
221
-
222
- this.suggestions.forEach(function(text) {
223
- me.ul.appendChild(me.item(text, value));
224
- });
225
-
226
- if (this.ul.children.length === 0) {
227
- this.close();
228
- } else {
229
- this.open();
230
- }
231
- }
232
- else {
233
- this.close();
234
- }
235
- }
236
- };
237
-
238
- // Static methods/properties
239
-
240
- _.all = [];
241
-
242
- _.FILTER_CONTAINS = function (text, input) {
243
- return RegExp($.regExpEscape(input.trim()), "i").test(text);
244
- };
245
-
246
- _.FILTER_STARTSWITH = function (text, input) {
247
- return RegExp("^" + $.regExpEscape(input.trim()), "i").test(text);
248
- };
249
-
250
- _.SORT_BYLENGTH = function (a, b) {
251
- if (a.length !== b.length) {
252
- return a.length - b.length;
253
- }
254
-
255
- return a < b? -1 : 1;
256
- };
257
-
258
- _.ITEM = function (text, input) {
259
- var html = input === '' ? text : text.replace(RegExp($.regExpEscape(input.trim()), "gi"), "<mark>$&</mark>");
260
- return $.create("li", {
261
- innerHTML: html,
262
- "aria-selected": "false"
263
- });
264
- };
265
-
266
- _.REPLACE = function (text) {
267
- this.input.value = text;
268
- };
269
-
270
- // Private functions
271
-
272
- function configure(instance, properties, o) {
273
- for (var i in properties) {
274
- var initial = properties[i],
275
- attrValue = instance.input.getAttribute("data-" + i.toLowerCase());
276
-
277
- if (typeof initial === "number") {
278
- instance[i] = parseInt(attrValue);
279
- }
280
- else if (initial === false) { // Boolean options must be false by default anyway
281
- instance[i] = attrValue !== null;
282
- }
283
- else if (initial instanceof Function) {
284
- instance[i] = null;
285
- }
286
- else {
287
- instance[i] = attrValue;
288
- }
289
-
290
- if (!instance[i] && instance[i] !== 0) {
291
- instance[i] = (i in o)? o[i] : initial;
292
- }
293
- }
294
- }
295
-
296
- // Helpers
297
-
298
- var slice = Array.prototype.slice;
299
-
300
- function $(expr, con) {
301
- return typeof expr === "string"? (con || document).querySelector(expr) : expr || null;
302
- }
303
-
304
- function $$(expr, con) {
305
- return slice.call((con || document).querySelectorAll(expr));
306
- }
307
-
308
- $.create = function(tag, o) {
309
- var element = document.createElement(tag);
310
-
311
- for (var i in o) {
312
- var val = o[i];
313
-
314
- if (i === "inside") {
315
- $(val).appendChild(element);
316
- }
317
- else if (i === "around") {
318
- var ref = $(val);
319
- ref.parentNode.insertBefore(element, ref);
320
- element.appendChild(ref);
321
- }
322
- else if (i in element) {
323
- element[i] = val;
324
- }
325
- else {
326
- element.setAttribute(i, val);
327
- }
328
- }
329
-
330
- return element;
331
- };
332
-
333
- $.bind = function(element, o) {
334
- if (element) {
335
- for (var event in o) {
336
- var callback = o[event];
337
-
338
- event.split(/\s+/).forEach(function (event) {
339
- element.addEventListener(event, callback);
340
- });
341
- }
342
- }
343
- };
344
-
345
- $.fire = function(target, type, properties) {
346
- var evt = document.createEvent("HTMLEvents");
347
-
348
- evt.initEvent(type, true, true );
349
-
350
- for (var j in properties) {
351
- evt[j] = properties[j];
352
- }
353
-
354
- return target.dispatchEvent(evt);
355
- };
356
-
357
- $.regExpEscape = function (s) {
358
- return s.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&");
359
- };
360
-
361
- $.siblingIndex = function (el) {
362
- /* eslint-disable no-cond-assign */
363
- for (var i = 0; el = el.previousElementSibling; i++);
364
- return i;
365
- };
366
-
367
- // Initialization
368
-
369
- function init() {
370
- $$("input.awesomplete").forEach(function (input) {
371
- new _(input);
372
- });
373
- }
374
-
375
- // Are we in a browser? Check for Document constructor
376
- if (typeof Document !== "undefined") {
377
- // DOM already loaded?
378
- if (document.readyState !== "loading") {
379
- init();
380
- }
381
- else {
382
- // Wait for it
383
- document.addEventListener("DOMContentLoaded", init);
384
- }
385
- }
386
-
387
- _.$ = $;
388
- _.$$ = $$;
389
-
390
- // Make sure to export Awesomplete on self when in a browser
391
- if (typeof self !== "undefined") {
392
- self.Awesomplete = _;
393
- }
394
-
395
- // Expose Awesomplete as a CJS module
396
- if (typeof module === "object" && module.exports) {
397
- module.exports = _;
398
- }
399
-
400
- return _;
401
-
402
- }());
@@ -1,140 +0,0 @@
1
- #= require_self
2
- #= require_directory .
3
- #= require_tree .
4
-
5
- class window.Fustrate
6
- @VERSION: '0.4.1'
7
- @libs: {}
8
-
9
- constructor: ->
10
- lib.init() for own name, lib of @constructor.libs
11
-
12
- for own component of Fustrate.Components
13
- Fustrate.Components[component].initialize()
14
-
15
- @initialize()
16
-
17
- initialize: ->
18
- # Loop through every element on the page with a data-js-class attribute
19
- # and convert the data attribute's value to a real object. Then instantiate
20
- # a new object of that class.
21
- $('[data-js-class]').each (index, elem) ->
22
- element = $(elem)
23
- klass = Fustrate._stringToClass element.data('js-class')
24
-
25
- element.data 'js-object', new klass(element)
26
-
27
- $.ajaxSetup
28
- cache: false
29
- beforeSend: $.rails.CSRFProtection
30
-
31
- $('table').wrap '<div class="responsive-table"></div>'
32
-
33
- $('.number').each (index, elem) ->
34
- elem = $ @
35
-
36
- number = if elem.data('number') isnt undefined
37
- elem.data('number')
38
- else
39
- elem.html()
40
-
41
- elem.addClass 'negative' if parseInt(number, 10) < 0
42
-
43
- # Take a string like 'Asgard.Whiteboard.Entry' and retrieve the real class
44
- # with that name. Start at `window` and work down from there.
45
- @_stringToClass: (string) ->
46
- pieces = string.split('.')
47
-
48
- Fustrate._arrayToClass(pieces, window)
49
-
50
- @_arrayToClass: (pieces, root) ->
51
- return root[pieces[0]] if pieces.length is 1
52
-
53
- Fustrate._arrayToClass pieces.slice(1), root[pieces[0]]
54
-
55
- # Very similar to the Rails helper `link_to`. Returns an HTML string.
56
- @linkTo: (text, path, options = {}) ->
57
- $('<a>').prop('href', path).html(text).prop(options).outerHTML()
58
-
59
- @ajaxUpload: (url, data) ->
60
- formData = new FormData
61
-
62
- formData.append key, value for key, value of data
63
-
64
- $.ajax
65
- url: url
66
- type: 'POST'
67
- data: formData
68
- processData: false
69
- contentType: false
70
- dataType: 'script'
71
- beforeSend: (xhr) ->
72
- $.rails.CSRFProtection xhr
73
-
74
- @humanDate: (date, time = false) ->
75
- if date.year() is moment().year()
76
- date.format("M/D#{if time then ' h:mm A' else ''}")
77
- else
78
- date.format("M/D/YY#{if time then ' h:mm A' else ''}")
79
-
80
- @label: (text, type) ->
81
- type = if type then "#{type} " else ''
82
-
83
- $('<span>')
84
- .text(text)
85
- .prop('class', "label #{type}#{text}".toLowerCase())
86
-
87
- @icon: (types) ->
88
- classes = ("fa-#{type}" for type in types.split(' ')).join(' ')
89
-
90
- "<i class=\"fa #{classes}\"></i>"
91
-
92
- # Replicate a few common prototype methods on String and Array
93
- String::titleize = ->
94
- @replace(/_/g, ' ').replace /\b[a-z]/g, (char) -> char.toUpperCase()
95
-
96
- String::capitalize = ->
97
- @charAt(0).toUpperCase() + @slice(1)
98
-
99
- String::phoneFormat = ->
100
- if /^(\d+)(ext|x)(\d+)$/.test @
101
- @replace /(\d+)(ext|x)(\d+)/, ->
102
- arguments[1].phoneFormat() + ' x' + arguments[3]
103
- else if /^1\d{10}$/.test @
104
- @replace /1(\d{3})(\d{3})(\d{4})/, '1 ($1) $2-$3'
105
- else if /^\d{10}$/.test @
106
- @replace /(\d{3})(\d{3})(\d{4})/, '($1) $2-$3'
107
- else if /^\d{7}$/.test @
108
- @replace /(\d{3})(\d{4})/, '$1-$2'
109
- else
110
- @
111
-
112
- Array::toSentence = ->
113
- switch @length
114
- when 0 then ''
115
- when 1 then @[0]
116
- when 2 then "#{@[0]} and #{@[1]}"
117
- else "#{@slice(0, -1).join(', ')}, and #{@[@length - 1]}"
118
-
119
- Array::remove = (object) ->
120
- index = @indexOf object
121
- @splice index, 1 if index isnt -1
122
-
123
- Array::first = ->
124
- @[0]
125
-
126
- Array::last = ->
127
- @[@length - 1]
128
-
129
- Array::peek = Array::last
130
-
131
- # Used to define getters and setters
132
- Function::define = (name, methods) ->
133
- Object.defineProperty @::, name, methods
134
-
135
- jQuery.fn.outerHTML = ->
136
- return '' unless @length
137
-
138
- return @[0].outerHTML if @[0].outerHTML
139
-
140
- $('<div>').append(@[0].clone()).remove().html()
@@ -1,3 +0,0 @@
1
- class Fustrate.Components
2
- class @Base extends Fustrate.Listenable
3
- @initialize: ->
@@ -1,10 +0,0 @@
1
- class Fustrate.Components.AlertBox extends Fustrate.Components.Base
2
- @fadeSpeed: 300
3
-
4
- @initialize: ->
5
- $('.alert-box').on 'click', '.close', (e) ->
6
- alert_box = $(e.currentTarget).closest('.alert-box')
7
-
8
- alert_box.fadeOut @constructor.fadeSpeed, alert_box.remove
9
-
10
- false
@@ -1,161 +0,0 @@
1
- class Fustrate.Components.Autocomplete extends Fustrate.Components.Base
2
- @types:
3
- plain:
4
- displayKey: 'value'
5
- item: (object, userInput) -> "<li>#{@highlight object.value}</li>"
6
- filter: (object, userInput) -> object.value.indexOf(userInput) >= 0
7
-
8
- @initialize: ->
9
- # Override the default sort
10
- Awesomplete.SORT_BYLENGTH = ->
11
-
12
- constructor: (@input, types) ->
13
- if $.isArray types
14
- types = {
15
- plain:
16
- list: types.map (value) -> { value: value }
17
- }
18
-
19
- @sources = for own type, options of types
20
- $.extend({}, @constructor.types[type], options)
21
-
22
- @sources = [@sources] if $.isPlainObject @sources
23
-
24
- existing = @input.data('awesomplete')
25
-
26
- if existing
27
- existing.sources = @sources
28
- return
29
-
30
- @awesomplete = new Awesomplete(
31
- @input[0]
32
- minChars: 0
33
- maxItems: 25
34
- filter: -> true
35
- item: (option, userInput) -> option # Items are pre-rendered
36
- )
37
-
38
- @input
39
- .data 'awesomplete', @
40
- .on 'awesomplete-highlight', @onHighlight
41
- .on 'awesomplete-select', @onSelect
42
- .on 'keyup', @constructor.debounce(@onKeyup)
43
- .on 'focus', @onFocus
44
-
45
- blanked: =>
46
- return unless @input.val().trim() is ''
47
-
48
- @awesomplete.close()
49
-
50
- $("~ input:hidden##{@input.attr('id')}_id", @awesomplete.container)
51
- .val null
52
- $("~ input:hidden##{@input.attr('id')}_type", @awesomplete.container)
53
- .val null
54
-
55
- @input.trigger 'blanked.autocomplete'
56
-
57
- onFocus: =>
58
- @items = []
59
- @value = @input.val().trim() ? ''
60
-
61
- for source in @sources when source.list?.length
62
- for datum in source.list when source.filter(datum, @value)
63
- @items.push @createListItem(datum, source)
64
-
65
- @awesomplete.list = @items
66
-
67
- onHighlight: =>
68
- item = $('+ ul li[aria-selected="true"]', @input)
69
-
70
- return unless item[0]
71
-
72
- item[0].scrollIntoView false
73
- @replace item.data('datum')._displayValue
74
-
75
- onSelect: (e) =>
76
- # aria-selected isn't set on click
77
- item = $(e.originalEvent.origin).closest('li')
78
- datum = item.data('datum')
79
-
80
- @replace datum._displayValue
81
- @awesomplete.close()
82
-
83
- $("~ input:hidden##{@input.attr('id')}_id", @awesomplete.container)
84
- .val datum.id
85
- $("~ input:hidden##{@input.attr('id')}_type", @awesomplete.container)
86
- .val datum._type
87
-
88
- @input.data(datum: datum).trigger('finished.autocomplete')
89
-
90
- false
91
-
92
- onKeyup: (e) =>
93
- keyCode = e.which or e.keyCode
94
-
95
- value = @input.val().trim()
96
-
97
- return @blanked() if value is ''
98
-
99
- # Ignore: Tab, Enter, Esc, Left, Up, Right, Down
100
- return if keyCode in [9, 13, 27, 37, 38, 39, 40]
101
-
102
- # Don't perform the same search twice in a row
103
- return unless value isnt @value and value.length >= 2
104
-
105
- @value = value
106
- @items = []
107
-
108
- for source in @sources
109
- if source.url?
110
- @performSearch(source)
111
- else if source.list?
112
- for datum in source.list when source.filter(datum, @value)
113
- @items.push @createListItem(datum, source)
114
-
115
- @awesomplete.list = @items
116
-
117
- performSearch: (source) =>
118
- $.get source.url(search: @value, commit: 1, format: 'json')
119
- .done (response) =>
120
- for datum in response
121
- @items.push @createListItem(datum, source)
122
-
123
- @awesomplete.list = @items
124
-
125
- createListItem: (datum, source) ->
126
- datum._displayValue = datum[source.displayKey]
127
- datum._type = source.type
128
-
129
- $ source.item.call(@, datum, @value)
130
- .data datum: datum
131
- .get(0)
132
-
133
- highlight: (text) =>
134
- return '' unless text
135
-
136
- text.replace RegExp("(#{@value.split(/\s+/).join('|')})", 'gi'),
137
- '<mark>$&</mark>'
138
-
139
- replace: (text) =>
140
- @awesomplete.replace(text)
141
-
142
- @debounce: (func, milliseconds = 300, immediate = false) ->
143
- timeout = null
144
-
145
- (args...) ->
146
- delayed = =>
147
- func.apply(@, args) unless immediate
148
- timeout = null
149
-
150
- if timeout
151
- clearTimeout(timeout)
152
- else if immediate
153
- func.apply(@, args)
154
-
155
- timeout = setTimeout delayed, milliseconds
156
-
157
- @addType: (name, func) =>
158
- @types[name] = func
159
-
160
- @addTypes: (types) ->
161
- @addType(name, func) for own name, func of types