schema_designer 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +34 -0
  4. data/app/assets/javascripts/schema_designer/_potential_schema.js.coffee +31 -0
  5. data/app/assets/javascripts/schema_designer/application.js +16 -0
  6. data/app/assets/javascripts/schema_designer/backbone.js +1581 -0
  7. data/app/assets/javascripts/schema_designer/jquery.autocomplete.js +645 -0
  8. data/app/assets/javascripts/schema_designer/schema.js +181 -0
  9. data/app/assets/javascripts/schema_designer/springy.js +697 -0
  10. data/app/assets/javascripts/schema_designer/underscore.js +1276 -0
  11. data/app/assets/stylesheets/schema_designer/application.css +13 -0
  12. data/app/assets/stylesheets/schema_designer/schema.css +35 -0
  13. data/app/controllers/schema_designer/application_controller.rb +6 -0
  14. data/app/controllers/schema_designer/schema_controller.rb +137 -0
  15. data/app/views/layouts/schema_designer/application.html.erb +14 -0
  16. data/app/views/schema_designer/schema/index.html.erb +19 -0
  17. data/config/routes.rb +10 -0
  18. data/lib/schema_designer.rb +4 -0
  19. data/lib/schema_designer/engine.rb +5 -0
  20. data/lib/schema_designer/version.rb +3 -0
  21. data/test/controllers/schema_designer/schema_controller_test.rb +11 -0
  22. data/test/dummy/README.rdoc +28 -0
  23. data/test/dummy/Rakefile +6 -0
  24. data/test/dummy/app/assets/javascripts/application.js +13 -0
  25. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  26. data/test/dummy/app/controllers/application_controller.rb +5 -0
  27. data/test/dummy/app/helpers/application_helper.rb +2 -0
  28. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  29. data/test/dummy/bin/bundle +3 -0
  30. data/test/dummy/bin/rails +4 -0
  31. data/test/dummy/bin/rake +4 -0
  32. data/test/dummy/config.ru +4 -0
  33. data/test/dummy/config/application.rb +23 -0
  34. data/test/dummy/config/boot.rb +5 -0
  35. data/test/dummy/config/database.yml +25 -0
  36. data/test/dummy/config/environment.rb +5 -0
  37. data/test/dummy/config/environments/development.rb +29 -0
  38. data/test/dummy/config/environments/production.rb +80 -0
  39. data/test/dummy/config/environments/test.rb +36 -0
  40. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  41. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  42. data/test/dummy/config/initializers/inflections.rb +16 -0
  43. data/test/dummy/config/initializers/mime_types.rb +5 -0
  44. data/test/dummy/config/initializers/secret_token.rb +12 -0
  45. data/test/dummy/config/initializers/session_store.rb +3 -0
  46. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  47. data/test/dummy/config/locales/en.yml +23 -0
  48. data/test/dummy/config/routes.rb +4 -0
  49. data/test/dummy/public/404.html +58 -0
  50. data/test/dummy/public/422.html +58 -0
  51. data/test/dummy/public/500.html +57 -0
  52. data/test/dummy/public/favicon.ico +0 -0
  53. data/test/helpers/schema_designer/schema_helper_test.rb +6 -0
  54. data/test/integration/navigation_test.rb +10 -0
  55. data/test/schema_designer_test.rb +7 -0
  56. data/test/test_helper.rb +15 -0
  57. metadata +163 -0
@@ -0,0 +1,645 @@
1
+ /**
2
+ * Ajax Autocomplete for jQuery, version 1.2.7
3
+ * (c) 2013 Tomas Kirda
4
+ *
5
+ * Ajax Autocomplete for jQuery is freely distributable under the terms of an MIT-style license.
6
+ * For details, see the web site: http://www.devbridge.com/projects/autocomplete/jquery/
7
+ *
8
+ */
9
+
10
+ /*jslint browser: true, white: true, plusplus: true */
11
+ /*global define, window, document, jQuery */
12
+
13
+ // Expose plugin as an AMD module if AMD loader is present:
14
+ (function (factory) {
15
+ 'use strict';
16
+ if (typeof define === 'function' && define.amd) {
17
+ // AMD. Register as an anonymous module.
18
+ define(['jquery'], factory);
19
+ } else {
20
+ // Browser globals
21
+ factory(jQuery);
22
+ }
23
+ }(function ($) {
24
+ 'use strict';
25
+
26
+ var
27
+ utils = (function () {
28
+ return {
29
+
30
+ extend: function (target, source) {
31
+ return $.extend(target, source);
32
+ },
33
+
34
+ createNode: function (html) {
35
+ var div = document.createElement('div');
36
+ div.innerHTML = html;
37
+ return div.firstChild;
38
+ }
39
+
40
+ };
41
+ }()),
42
+
43
+ keys = {
44
+ ESC: 27,
45
+ TAB: 9,
46
+ RETURN: 13,
47
+ UP: 38,
48
+ DOWN: 40
49
+ };
50
+
51
+ function Autocomplete(el, options) {
52
+ var noop = function () { },
53
+ that = this,
54
+ defaults = {
55
+ autoSelectFirst: false,
56
+ appendTo: 'body',
57
+ serviceUrl: null,
58
+ lookup: null,
59
+ onSelect: null,
60
+ width: 'auto',
61
+ minChars: 1,
62
+ maxHeight: 300,
63
+ deferRequestBy: 0,
64
+ params: {},
65
+ formatResult: Autocomplete.formatResult,
66
+ delimiter: null,
67
+ zIndex: 9999,
68
+ type: 'GET',
69
+ noCache: false,
70
+ onSearchStart: noop,
71
+ onSearchComplete: noop,
72
+ containerClass: 'autocomplete-suggestions',
73
+ tabDisabled: false,
74
+ dataType: 'text',
75
+ lookupFilter: function (suggestion, originalQuery, queryLowerCase) {
76
+ return suggestion.value.toLowerCase().indexOf(queryLowerCase) !== -1;
77
+ },
78
+ paramName: 'query',
79
+ transformResult: function (response) {
80
+ return typeof response === 'string' ? $.parseJSON(response) : response;
81
+ }
82
+ };
83
+
84
+ // Shared variables:
85
+ that.element = el;
86
+ that.el = $(el);
87
+ that.suggestions = [];
88
+ that.badQueries = [];
89
+ that.selectedIndex = -1;
90
+ that.currentValue = that.element.value;
91
+ that.intervalId = 0;
92
+ that.cachedResponse = [];
93
+ that.onChangeInterval = null;
94
+ that.onChange = null;
95
+ that.ignoreValueChange = false;
96
+ that.isLocal = false;
97
+ that.suggestionsContainer = null;
98
+ that.options = $.extend({}, defaults, options);
99
+ that.classes = {
100
+ selected: 'autocomplete-selected',
101
+ suggestion: 'autocomplete-suggestion'
102
+ };
103
+
104
+ // Initialize and set options:
105
+ that.initialize();
106
+ that.setOptions(options);
107
+ }
108
+
109
+ Autocomplete.utils = utils;
110
+
111
+ $.Autocomplete = Autocomplete;
112
+
113
+ Autocomplete.formatResult = function (suggestion, currentValue) {
114
+ var reEscape = new RegExp('(\\' + ['/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\'].join('|\\') + ')', 'g'),
115
+ pattern = '(' + currentValue.replace(reEscape, '\\$1') + ')';
116
+
117
+ return suggestion.value.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
118
+ };
119
+
120
+ Autocomplete.prototype = {
121
+
122
+ killerFn: null,
123
+
124
+ initialize: function () {
125
+ var that = this,
126
+ suggestionSelector = '.' + that.classes.suggestion,
127
+ selected = that.classes.selected,
128
+ options = that.options,
129
+ container;
130
+
131
+ // Remove autocomplete attribute to prevent native suggestions:
132
+ that.element.setAttribute('autocomplete', 'off');
133
+
134
+ that.killerFn = function (e) {
135
+ if ($(e.target).closest('.' + that.options.containerClass).length === 0) {
136
+ that.killSuggestions();
137
+ that.disableKillerFn();
138
+ }
139
+ };
140
+
141
+ // Determine suggestions width:
142
+ if (!options.width || options.width === 'auto') {
143
+ options.width = that.el.outerWidth();
144
+ }
145
+
146
+ that.suggestionsContainer = Autocomplete.utils.createNode('<div class="' + options.containerClass + '" style="position: absolute; display: none;"></div>');
147
+
148
+ container = $(that.suggestionsContainer);
149
+
150
+ container.appendTo(options.appendTo).width(options.width);
151
+
152
+ // Listen for mouse over event on suggestions list:
153
+ container.on('mouseover.autocomplete', suggestionSelector, function () {
154
+ that.activate($(this).data('index'));
155
+ });
156
+
157
+ // Deselect active element when mouse leaves suggestions container:
158
+ container.on('mouseout.autocomplete', function () {
159
+ that.selectedIndex = -1;
160
+ container.children('.' + selected).removeClass(selected);
161
+ });
162
+
163
+ // Listen for click event on suggestions list:
164
+ container.on('click.autocomplete', suggestionSelector, function () {
165
+ that.select($(this).data('index'), false);
166
+ });
167
+
168
+ that.fixPosition();
169
+
170
+ // Opera does not like keydown:
171
+ if (window.opera) {
172
+ that.el.on('keypress.autocomplete', function (e) { that.onKeyPress(e); });
173
+ } else {
174
+ that.el.on('keydown.autocomplete', function (e) { that.onKeyPress(e); });
175
+ }
176
+
177
+ that.el.on('keyup.autocomplete', function (e) { that.onKeyUp(e); });
178
+ that.el.on('blur.autocomplete', function () { that.onBlur(); });
179
+ that.el.on('focus.autocomplete', function () { that.fixPosition(); });
180
+ },
181
+
182
+ onBlur: function () {
183
+ this.enableKillerFn();
184
+ },
185
+
186
+ setOptions: function (suppliedOptions) {
187
+ var that = this,
188
+ options = that.options;
189
+
190
+ utils.extend(options, suppliedOptions);
191
+
192
+ that.isLocal = $.isArray(options.lookup);
193
+
194
+ if (that.isLocal) {
195
+ options.lookup = that.verifySuggestionsFormat(options.lookup);
196
+ }
197
+
198
+ // Adjust height, width and z-index:
199
+ $(that.suggestionsContainer).css({
200
+ 'max-height': options.maxHeight + 'px',
201
+ 'width': options.width + 'px',
202
+ 'z-index': options.zIndex
203
+ });
204
+ },
205
+
206
+ clearCache: function () {
207
+ this.cachedResponse = [];
208
+ this.badQueries = [];
209
+ },
210
+
211
+ clear: function () {
212
+ this.clearCache();
213
+ this.currentValue = null;
214
+ this.suggestions = [];
215
+ },
216
+
217
+ disable: function () {
218
+ this.disabled = true;
219
+ },
220
+
221
+ enable: function () {
222
+ this.disabled = false;
223
+ },
224
+
225
+ fixPosition: function () {
226
+ var that = this,
227
+ offset;
228
+
229
+ // Don't adjsut position if custom container has been specified:
230
+ if (that.options.appendTo !== 'body') {
231
+ return;
232
+ }
233
+
234
+ offset = that.el.offset();
235
+
236
+ $(that.suggestionsContainer).css({
237
+ top: (offset.top + that.el.outerHeight()) + 'px',
238
+ left: offset.left + 'px'
239
+ });
240
+ },
241
+
242
+ enableKillerFn: function () {
243
+ var that = this;
244
+ $(document).on('click.autocomplete', that.killerFn);
245
+ },
246
+
247
+ disableKillerFn: function () {
248
+ var that = this;
249
+ $(document).off('click.autocomplete', that.killerFn);
250
+ },
251
+
252
+ killSuggestions: function () {
253
+ var that = this;
254
+ that.stopKillSuggestions();
255
+ that.intervalId = window.setInterval(function () {
256
+ that.hide();
257
+ that.stopKillSuggestions();
258
+ }, 300);
259
+ },
260
+
261
+ stopKillSuggestions: function () {
262
+ window.clearInterval(this.intervalId);
263
+ },
264
+
265
+ onKeyPress: function (e) {
266
+ var that = this;
267
+
268
+ // If suggestions are hidden and user presses arrow down, display suggestions:
269
+ if (!that.disabled && !that.visible && e.keyCode === keys.DOWN && that.currentValue) {
270
+ that.suggest();
271
+ return;
272
+ }
273
+
274
+ if (that.disabled || !that.visible) {
275
+ return;
276
+ }
277
+
278
+ switch (e.keyCode) {
279
+ case keys.ESC:
280
+ that.el.val(that.currentValue);
281
+ that.hide();
282
+ break;
283
+ case keys.TAB:
284
+ case keys.RETURN:
285
+ if (that.selectedIndex === -1) {
286
+ that.hide();
287
+ return;
288
+ }
289
+ that.select(that.selectedIndex, e.keyCode === keys.RETURN);
290
+ if (e.keyCode === keys.TAB && this.options.tabDisabled === false) {
291
+ return;
292
+ }
293
+ break;
294
+ case keys.UP:
295
+ that.moveUp();
296
+ break;
297
+ case keys.DOWN:
298
+ that.moveDown();
299
+ break;
300
+ default:
301
+ return;
302
+ }
303
+
304
+ // Cancel event if function did not return:
305
+ e.stopImmediatePropagation();
306
+ e.preventDefault();
307
+ },
308
+
309
+ onKeyUp: function (e) {
310
+ var that = this;
311
+
312
+ if (that.disabled) {
313
+ return;
314
+ }
315
+
316
+ switch (e.keyCode) {
317
+ case keys.UP:
318
+ case keys.DOWN:
319
+ return;
320
+ }
321
+
322
+ clearInterval(that.onChangeInterval);
323
+
324
+ if (that.currentValue !== that.el.val()) {
325
+ if (that.options.deferRequestBy > 0) {
326
+ // Defer lookup in case when value changes very quickly:
327
+ that.onChangeInterval = setInterval(function () {
328
+ that.onValueChange();
329
+ }, that.options.deferRequestBy);
330
+ } else {
331
+ that.onValueChange();
332
+ }
333
+ }
334
+ },
335
+
336
+ onValueChange: function () {
337
+ var that = this,
338
+ q;
339
+
340
+ clearInterval(that.onChangeInterval);
341
+ that.currentValue = that.element.value;
342
+
343
+ q = that.getQuery(that.currentValue);
344
+ that.selectedIndex = -1;
345
+
346
+ if (that.ignoreValueChange) {
347
+ that.ignoreValueChange = false;
348
+ return;
349
+ }
350
+
351
+ if (q.length < that.options.minChars) {
352
+ that.hide();
353
+ } else {
354
+ that.getSuggestions(q);
355
+ }
356
+ },
357
+
358
+ getQuery: function (value) {
359
+ var delimiter = this.options.delimiter,
360
+ parts;
361
+
362
+ if (!delimiter) {
363
+ return $.trim(value);
364
+ }
365
+ parts = value.split(delimiter);
366
+ return $.trim(parts[parts.length - 1]);
367
+ },
368
+
369
+ getSuggestionsLocal: function (query) {
370
+ var that = this,
371
+ queryLowerCase = query.toLowerCase(),
372
+ filter = that.options.lookupFilter;
373
+
374
+ return {
375
+ suggestions: $.grep(that.options.lookup, function (suggestion) {
376
+ return filter(suggestion, query, queryLowerCase);
377
+ })
378
+ };
379
+ },
380
+
381
+ getSuggestions: function (q) {
382
+ var response,
383
+ that = this,
384
+ options = that.options,
385
+ serviceUrl = options.serviceUrl;
386
+
387
+ response = that.isLocal ? that.getSuggestionsLocal(q) : that.cachedResponse[q];
388
+
389
+ if (response && $.isArray(response.suggestions)) {
390
+ that.suggestions = response.suggestions;
391
+ that.suggest();
392
+ } else if (!that.isBadQuery(q)) {
393
+ options.params[options.paramName] = q;
394
+ if (options.onSearchStart.call(that.element, options.params) === false) {
395
+ return;
396
+ }
397
+ if ($.isFunction(options.serviceUrl)) {
398
+ serviceUrl = options.serviceUrl.call(that.element, q);
399
+ }
400
+ $.ajax({
401
+ url: serviceUrl,
402
+ data: options.ignoreParams ? null : options.params,
403
+ type: options.type,
404
+ dataType: options.dataType
405
+ }).done(function (data) {
406
+ that.processResponse(data, q);
407
+ options.onSearchComplete.call(that.element, q);
408
+ });
409
+ }
410
+ },
411
+
412
+ isBadQuery: function (q) {
413
+ var badQueries = this.badQueries,
414
+ i = badQueries.length;
415
+
416
+ while (i--) {
417
+ if (q.indexOf(badQueries[i]) === 0) {
418
+ return true;
419
+ }
420
+ }
421
+
422
+ return false;
423
+ },
424
+
425
+ hide: function () {
426
+ var that = this;
427
+ that.visible = false;
428
+ that.selectedIndex = -1;
429
+ $(that.suggestionsContainer).hide();
430
+ },
431
+
432
+ suggest: function () {
433
+ if (this.suggestions.length === 0) {
434
+ this.hide();
435
+ return;
436
+ }
437
+
438
+ var that = this,
439
+ formatResult = that.options.formatResult,
440
+ value = that.getQuery(that.currentValue),
441
+ className = that.classes.suggestion,
442
+ classSelected = that.classes.selected,
443
+ container = $(that.suggestionsContainer),
444
+ html = '';
445
+
446
+ // Build suggestions inner HTML:
447
+ $.each(that.suggestions, function (i, suggestion) {
448
+ html += '<div class="' + className + '" data-index="' + i + '">' + formatResult(suggestion, value) + '</div>';
449
+ });
450
+
451
+ container.html(html).show();
452
+ that.visible = true;
453
+
454
+ // Select first value by default:
455
+ if (that.options.autoSelectFirst) {
456
+ that.selectedIndex = 0;
457
+ container.children().first().addClass(classSelected);
458
+ }
459
+ },
460
+
461
+ verifySuggestionsFormat: function (suggestions) {
462
+ // If suggestions is string array, convert them to supported format:
463
+ if (suggestions.length && typeof suggestions[0] === 'string') {
464
+ return $.map(suggestions, function (value) {
465
+ return { value: value, data: null };
466
+ });
467
+ }
468
+
469
+ return suggestions;
470
+ },
471
+
472
+ processResponse: function (response, originalQuery) {
473
+ var that = this,
474
+ options = that.options,
475
+ result = options.transformResult(response, originalQuery);
476
+
477
+ result.suggestions = that.verifySuggestionsFormat(result.suggestions);
478
+
479
+ // Cache results if cache is not disabled:
480
+ if (!options.noCache) {
481
+ that.cachedResponse[result[options.paramName]] = result;
482
+ if (result.suggestions.length === 0) {
483
+ that.badQueries.push(result[options.paramName]);
484
+ }
485
+ }
486
+
487
+ // Display suggestions only if returned query matches current value:
488
+ if (originalQuery === that.getQuery(that.currentValue)) {
489
+ that.suggestions = result.suggestions;
490
+ that.suggest();
491
+ }
492
+ },
493
+
494
+ activate: function (index) {
495
+ var that = this,
496
+ activeItem,
497
+ selected = that.classes.selected,
498
+ container = $(that.suggestionsContainer),
499
+ children = container.children();
500
+
501
+ container.children('.' + selected).removeClass(selected);
502
+
503
+ that.selectedIndex = index;
504
+
505
+ if (that.selectedIndex !== -1 && children.length > that.selectedIndex) {
506
+ activeItem = children.get(that.selectedIndex);
507
+ $(activeItem).addClass(selected);
508
+ return activeItem;
509
+ }
510
+
511
+ return null;
512
+ },
513
+
514
+ select: function (i, shouldIgnoreNextValueChange) {
515
+ var that = this,
516
+ selectedValue = that.suggestions[i];
517
+
518
+ if (selectedValue) {
519
+ that.el.val(selectedValue);
520
+ that.ignoreValueChange = shouldIgnoreNextValueChange;
521
+ that.hide();
522
+ that.onSelect(i);
523
+ }
524
+ },
525
+
526
+ moveUp: function () {
527
+ var that = this;
528
+
529
+ if (that.selectedIndex === -1) {
530
+ return;
531
+ }
532
+
533
+ if (that.selectedIndex === 0) {
534
+ $(that.suggestionsContainer).children().first().removeClass(that.classes.selected);
535
+ that.selectedIndex = -1;
536
+ that.el.val(that.currentValue);
537
+ return;
538
+ }
539
+
540
+ that.adjustScroll(that.selectedIndex - 1);
541
+ },
542
+
543
+ moveDown: function () {
544
+ var that = this;
545
+
546
+ if (that.selectedIndex === (that.suggestions.length - 1)) {
547
+ return;
548
+ }
549
+
550
+ that.adjustScroll(that.selectedIndex + 1);
551
+ },
552
+
553
+ adjustScroll: function (index) {
554
+ var that = this,
555
+ activeItem = that.activate(index),
556
+ offsetTop,
557
+ upperBound,
558
+ lowerBound,
559
+ heightDelta = 25;
560
+
561
+ if (!activeItem) {
562
+ return;
563
+ }
564
+
565
+ offsetTop = activeItem.offsetTop;
566
+ upperBound = $(that.suggestionsContainer).scrollTop();
567
+ lowerBound = upperBound + that.options.maxHeight - heightDelta;
568
+
569
+ if (offsetTop < upperBound) {
570
+ $(that.suggestionsContainer).scrollTop(offsetTop);
571
+ } else if (offsetTop > lowerBound) {
572
+ $(that.suggestionsContainer).scrollTop(offsetTop - that.options.maxHeight + heightDelta);
573
+ }
574
+
575
+ that.el.val(that.getValue(that.suggestions[index].value));
576
+ },
577
+
578
+ onSelect: function (index) {
579
+ var that = this,
580
+ onSelectCallback = that.options.onSelect,
581
+ suggestion = that.suggestions[index];
582
+
583
+ that.el.val(that.getValue(suggestion.value));
584
+
585
+ if ($.isFunction(onSelectCallback)) {
586
+ onSelectCallback.call(that.element, suggestion);
587
+ }
588
+ },
589
+
590
+ getValue: function (value) {
591
+ var that = this,
592
+ delimiter = that.options.delimiter,
593
+ currentValue,
594
+ parts;
595
+
596
+ if (!delimiter) {
597
+ return value;
598
+ }
599
+
600
+ currentValue = that.currentValue;
601
+ parts = currentValue.split(delimiter);
602
+
603
+ if (parts.length === 1) {
604
+ return value;
605
+ }
606
+
607
+ return currentValue.substr(0, currentValue.length - parts[parts.length - 1].length) + value;
608
+ },
609
+
610
+ dispose: function () {
611
+ var that = this;
612
+ that.el.off('.autocomplete').removeData('autocomplete');
613
+ that.disableKillerFn();
614
+ $(that.suggestionsContainer).remove();
615
+ }
616
+ };
617
+
618
+ // Create chainable jQuery plugin:
619
+ $.fn.autocomplete = function (options, args) {
620
+ var dataKey = 'autocomplete';
621
+ // If function invoked without argument return
622
+ // instance of the first matched element:
623
+ if (arguments.length === 0) {
624
+ return this.first().data(dataKey);
625
+ }
626
+
627
+ return this.each(function () {
628
+ var inputElement = $(this),
629
+ instance = inputElement.data(dataKey);
630
+
631
+ if (typeof options === 'string') {
632
+ if (instance && typeof instance[options] === 'function') {
633
+ instance[options](args);
634
+ }
635
+ } else {
636
+ // If instance already exists, destroy it:
637
+ if (instance && instance.dispose) {
638
+ instance.dispose();
639
+ }
640
+ instance = new Autocomplete(this, options);
641
+ inputElement.data(dataKey, instance);
642
+ }
643
+ });
644
+ };
645
+ }));