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.
- checksums.yaml +5 -5
- data/config/initializers/jbuilder.rb +13 -0
- data/config/initializers/renderers.rb +40 -0
- data/config/initializers/sanitize.rb +112 -0
- data/config/rubocop/default.yml +96 -0
- data/config/rubocop/rails.yml +22 -0
- data/lib/fustrate/rails/concerns/clean_attributes.rb +49 -0
- data/lib/fustrate/rails/concerns/model.rb +48 -0
- data/lib/fustrate/rails/concerns/sanitize_html.rb +34 -0
- data/lib/fustrate/rails/engine.rb +14 -7
- data/lib/fustrate/rails/services/base.rb +46 -0
- data/lib/fustrate/rails/services/generate_csv.rb +35 -0
- data/lib/fustrate/rails/services/generate_excel.rb +32 -0
- data/lib/fustrate/rails/services/log_edit.rb +132 -0
- data/lib/fustrate/rails/spec_helper.rb +62 -0
- data/lib/fustrate/rails/version.rb +5 -1
- data/lib/fustrate-rails.rb +5 -2
- metadata +44 -140
- data/vendor/assets/javascripts/awesomplete.js +0 -402
- data/vendor/assets/javascripts/fustrate/_module.coffee +0 -140
- data/vendor/assets/javascripts/fustrate/components/_module.coffee +0 -3
- data/vendor/assets/javascripts/fustrate/components/alert_box.coffee +0 -10
- data/vendor/assets/javascripts/fustrate/components/autocomplete.coffee +0 -161
- data/vendor/assets/javascripts/fustrate/components/disclosure.coffee +0 -12
- data/vendor/assets/javascripts/fustrate/components/drop_zone.coffee +0 -9
- data/vendor/assets/javascripts/fustrate/components/dropdown.coffee +0 -48
- data/vendor/assets/javascripts/fustrate/components/file_picker.coffee +0 -11
- data/vendor/assets/javascripts/fustrate/components/flash.coffee +0 -31
- data/vendor/assets/javascripts/fustrate/components/modal.coffee +0 -273
- data/vendor/assets/javascripts/fustrate/components/pagination.coffee +0 -84
- data/vendor/assets/javascripts/fustrate/components/tabs.coffee +0 -28
- data/vendor/assets/javascripts/fustrate/components/tooltip.coffee +0 -72
- data/vendor/assets/javascripts/fustrate/generic_form.coffee +0 -30
- data/vendor/assets/javascripts/fustrate/generic_page.coffee +0 -40
- data/vendor/assets/javascripts/fustrate/generic_table.coffee +0 -57
- data/vendor/assets/javascripts/fustrate/listenable.coffee +0 -25
- data/vendor/assets/javascripts/fustrate/object.coffee +0 -21
- data/vendor/assets/javascripts/fustrate/record.coffee +0 -23
- data/vendor/assets/javascripts/fustrate.coffee +0 -6
- data/vendor/assets/stylesheets/_fustrate.sass +0 -7
- data/vendor/assets/stylesheets/awesomplete.sass +0 -76
- data/vendor/assets/stylesheets/fustrate/_colors.sass +0 -12
- data/vendor/assets/stylesheets/fustrate/_settings.sass +0 -20
- data/vendor/assets/stylesheets/fustrate/components/_components.sass +0 -36
- data/vendor/assets/stylesheets/fustrate/components/_functions.sass +0 -41
- data/vendor/assets/stylesheets/fustrate/components/alerts.sass +0 -86
- data/vendor/assets/stylesheets/fustrate/components/buttons.sass +0 -99
- data/vendor/assets/stylesheets/fustrate/components/disclosures.sass +0 -23
- data/vendor/assets/stylesheets/fustrate/components/dropdowns.sass +0 -36
- data/vendor/assets/stylesheets/fustrate/components/flash.sass +0 -38
- data/vendor/assets/stylesheets/fustrate/components/forms.sass +0 -195
- data/vendor/assets/stylesheets/fustrate/components/grid.sass +0 -196
- data/vendor/assets/stylesheets/fustrate/components/labels.sass +0 -64
- data/vendor/assets/stylesheets/fustrate/components/modals.sass +0 -167
- data/vendor/assets/stylesheets/fustrate/components/pagination.sass +0 -69
- data/vendor/assets/stylesheets/fustrate/components/panels.sass +0 -67
- data/vendor/assets/stylesheets/fustrate/components/popovers.sass +0 -19
- data/vendor/assets/stylesheets/fustrate/components/tables.sass +0 -62
- data/vendor/assets/stylesheets/fustrate/components/tabs.sass +0 -44
- data/vendor/assets/stylesheets/fustrate/components/tooltips.sass +0 -28
- 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,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
|