iqvoc 4.2.0 → 4.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (148) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -0
  3. data/Gemfile +15 -12
  4. data/Gemfile.lock +124 -90
  5. data/README.md +17 -1
  6. data/app/assets/images/iqvoc_logo.svg +41 -0
  7. data/app/assets/javascripts/framework.js +1 -1
  8. data/app/assets/javascripts/iqvoc/concept_mapper.js +62 -0
  9. data/app/assets/javascripts/iqvoc/concept_mapping_manager.js +137 -0
  10. data/app/assets/javascripts/iqvoc/entityselect.js.erb +51 -19
  11. data/app/assets/javascripts/iqvoc/federated_concept_mapper.js +75 -0
  12. data/app/assets/javascripts/iqvoc/iqvoc.js +16 -4
  13. data/app/assets/javascripts/iqvoc/label_resolver.js +23 -0
  14. data/app/assets/javascripts/iqvoc/manifest.js +4 -0
  15. data/app/assets/javascripts/iqvoc/onebox.js.erb +4 -4
  16. data/app/assets/javascripts/iqvoc/quicksearch.js +1 -1
  17. data/app/assets/stylesheets/framework.css.scss +2 -1
  18. data/app/assets/stylesheets/iqvoc/_settings.css.scss +12 -0
  19. data/app/assets/stylesheets/iqvoc/components/_components.css.scss +237 -0
  20. data/app/assets/stylesheets/iqvoc/{entity_select.css.scss → components/_entity_select.css.scss} +0 -2
  21. data/app/assets/stylesheets/iqvoc/{forms.css.scss → components/_forms.css.scss} +0 -0
  22. data/app/assets/stylesheets/iqvoc/{note.css.scss → components/_notes.css.scss} +2 -10
  23. data/app/assets/stylesheets/iqvoc/{panel.css.scss → components/_panels.css.scss} +3 -5
  24. data/app/assets/stylesheets/iqvoc/{sidebar.css.scss → components/_sidebars.css.scss} +0 -0
  25. data/app/assets/stylesheets/iqvoc/{visualization.css.scss → components/_visualization.css.scss} +0 -0
  26. data/app/assets/stylesheets/iqvoc/{bootstrap_extensions.css.scss → hacks/_bootstrap_extensions.css.scss} +0 -0
  27. data/app/assets/stylesheets/iqvoc/{hacks.css.scss → hacks/_hacks.css.scss} +0 -0
  28. data/app/assets/stylesheets/iqvoc/{jquery-ui_extensions.css.scss → hacks/_jquery-ui_extensions.css.scss} +0 -0
  29. data/app/assets/stylesheets/iqvoc/manifest.css.scss +10 -10
  30. data/app/controllers/concepts/alphabetical_controller.rb +23 -9
  31. data/app/controllers/concepts/scheme_controller.rb +19 -0
  32. data/app/controllers/concepts_controller.rb +13 -1
  33. data/app/controllers/concerns/dataset_initialization.rb +18 -0
  34. data/app/controllers/dashboard_controller.rb +17 -0
  35. data/app/controllers/hierarchy_controller.rb +65 -6
  36. data/app/controllers/{import_controller.rb → imports_controller.rb} +14 -11
  37. data/app/controllers/rdf_controller.rb +6 -0
  38. data/app/controllers/remote_labels_controller.rb +31 -0
  39. data/app/controllers/search_results_controller.rb +95 -14
  40. data/app/helpers/application_helper.rb +6 -4
  41. data/app/helpers/dashboard_helper.rb +3 -3
  42. data/app/helpers/link_helper.rb +14 -0
  43. data/app/helpers/search_results_helper.rb +5 -1
  44. data/app/jobs/import_job.rb +19 -0
  45. data/app/models/concept/base.rb +2 -2
  46. data/app/models/dataset/adaptors/iqvoc/alphabetical_search_adaptor.rb +38 -0
  47. data/app/models/dataset/adaptors/iqvoc/http_adaptor.rb +39 -0
  48. data/app/models/dataset/adaptors/iqvoc/label_adaptor.rb +15 -0
  49. data/app/models/dataset/adaptors/iqvoc/search_adaptor.rb +60 -0
  50. data/app/models/dataset/iqvoc_dataset.rb +39 -0
  51. data/app/models/import.rb +16 -0
  52. data/app/models/labeling/skos/base.rb +2 -1
  53. data/app/models/note/base.rb +2 -2
  54. data/app/models/note/skos/change_note.rb +3 -2
  55. data/app/presenters/alphabetical_search_result.rb +33 -0
  56. data/app/presenters/alphabetical_search_result_remote.rb +37 -0
  57. data/app/presenters/search_result.rb +33 -0
  58. data/app/presenters/search_result_collection.rb +8 -0
  59. data/app/presenters/search_result_remote.rb +52 -0
  60. data/app/views/collections/_sidebar.html.erb +3 -3
  61. data/app/views/concepts/_form.html.erb +1 -1
  62. data/app/views/concepts/alphabetical/_search_result.html.erb +16 -0
  63. data/app/views/concepts/alphabetical/_search_result_remote.html.erb +14 -0
  64. data/app/views/concepts/alphabetical/index.html.erb +5 -3
  65. data/app/views/concepts/expired/index.html.erb +5 -2
  66. data/app/views/concepts/scheme/_sidebar.html.erb +3 -3
  67. data/app/views/concepts/sidebars/_singular.html.erb +4 -4
  68. data/app/views/dashboard/_sidebar.html.erb +7 -4
  69. data/app/views/dashboard/index.html.erb +1 -1
  70. data/app/views/dashboard/reset.html.erb +6 -0
  71. data/app/views/imports/index.html.erb +44 -0
  72. data/app/views/imports/show.html.erb +7 -0
  73. data/app/views/layouts/application.html.erb +7 -1
  74. data/app/views/partials/labeling/skos/_search_result.html.erb +12 -16
  75. data/app/views/partials/match/_edit_base.html.erb +1 -1
  76. data/app/views/partials/note/_search_result.html.erb +8 -5
  77. data/app/views/partials/note/skos/change_note/_search_result.html.erb +7 -10
  78. data/app/views/partials/note/skos/definition/_search_result.html.erb +5 -5
  79. data/app/views/rdf/dataset.iqrdf +9 -0
  80. data/app/views/search_results/_search_result_remote.html.erb +13 -0
  81. data/app/views/search_results/_sidebar.html.erb +3 -3
  82. data/app/views/search_results/index.html.erb +4 -4
  83. data/app/views/search_results/index.iqrdf +7 -7
  84. data/app/views/search_results/sections/_datasets.html.erb +17 -0
  85. data/app/views/search_results/sections/_klass.html.erb +2 -2
  86. data/app/views/search_results/sections/_languages.html.erb +3 -3
  87. data/app/views/users/_sidebar.html.erb +1 -1
  88. data/config/application.rb +0 -2
  89. data/config/engine.rb +5 -0
  90. data/config/environments/heroku.rb +62 -16
  91. data/config/initializers/apipie.rb +17 -0
  92. data/config/initializers/secret_token.rb +25 -9
  93. data/config/locales/activerecord.de.yml +5 -0
  94. data/config/locales/activerecord.en.yml +5 -0
  95. data/config/locales/de.yml +14 -0
  96. data/config/locales/en.yml +14 -0
  97. data/config/locales/pt.yml +1 -0
  98. data/config/routes.rb +7 -4
  99. data/db/migrate/20131220134356_create_delayed_jobs.rb +22 -0
  100. data/db/migrate/20131220144601_create_imports.rb +11 -0
  101. data/db/schema.rb +26 -1
  102. data/iqvoc.gemspec +12 -3
  103. data/lib/iqvoc/ability.rb +2 -0
  104. data/lib/iqvoc/configuration/core.rb +36 -9
  105. data/lib/iqvoc/controller_extensions.rb +4 -0
  106. data/lib/iqvoc/environments/production.rb +1 -1
  107. data/lib/iqvoc/skos_importer.rb +31 -19
  108. data/lib/iqvoc/version.rb +1 -1
  109. data/lib/tasks/importer.rake +10 -2
  110. data/lib/tasks/release.rake +1 -1
  111. data/public/404.html +43 -11
  112. data/public/422.html +43 -11
  113. data/public/500.html +43 -12
  114. data/test/functional/{rdf_test.rb → rdf_rendering_test.rb} +1 -1
  115. data/test/integration/alphabetical_test.rb +5 -5
  116. data/test/integration/search_test.rb +16 -16
  117. data/test/unit/skos_import_test.rb +9 -9
  118. data/vendor/assets/images/animated-overlay.gif +0 -0
  119. data/vendor/assets/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  120. data/vendor/assets/images/ui-bg_flat_55_fbec88_40x100.png +0 -0
  121. data/vendor/assets/images/ui-bg_glass_75_d0e5f5_1x400.png +0 -0
  122. data/vendor/assets/images/ui-bg_glass_85_dfeffc_1x400.png +0 -0
  123. data/vendor/assets/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  124. data/vendor/assets/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png +0 -0
  125. data/vendor/assets/images/ui-bg_inset-hard_100_f5f8f9_1x100.png +0 -0
  126. data/vendor/assets/images/ui-bg_inset-hard_100_fcfdfd_1x100.png +0 -0
  127. data/vendor/assets/images/ui-icons_217bc0_256x240.png +0 -0
  128. data/vendor/assets/images/ui-icons_2e83ff_256x240.png +0 -0
  129. data/vendor/assets/images/ui-icons_469bdd_256x240.png +0 -0
  130. data/vendor/assets/images/ui-icons_6da8d5_256x240.png +0 -0
  131. data/vendor/assets/images/ui-icons_cd0a0a_256x240.png +0 -0
  132. data/vendor/assets/images/ui-icons_d8e7f3_256x240.png +0 -0
  133. data/vendor/assets/images/ui-icons_f9bd01_256x240.png +0 -0
  134. data/vendor/assets/javascripts/jquery-ui.custom.js +4538 -0
  135. data/vendor/assets/stylesheets/jquery-ui-1.10.3.custom.css.scss +724 -0
  136. metadata +214 -64
  137. data/app/assets/images/arrow_down.gif +0 -0
  138. data/app/assets/images/arrow_up.gif +0 -0
  139. data/app/assets/images/iqvoc_logo.png +0 -0
  140. data/app/assets/images/spinner_16x16.gif +0 -0
  141. data/app/assets/images/spinner_24x24.gif +0 -0
  142. data/app/assets/stylesheets/iqvoc/components.css.scss +0 -155
  143. data/app/assets/stylesheets/iqvoc/layout.css.scss +0 -0
  144. data/app/views/concepts/alphabetical/_pref_labeling.html.erb +0 -19
  145. data/app/views/import/import.html.erb +0 -7
  146. data/app/views/import/index.html.erb +0 -17
  147. data/vendor/assets/javascripts/jquery-ui-1.8.23.custom.js +0 -3399
  148. data/vendor/assets/stylesheets/jquery-ui-1.8.23.custom.css.scss +0 -405
data/README.md CHANGED
@@ -58,9 +58,25 @@ Running the cloned source code is possible but any modifications would require a
58
58
  8. Log in with "admin@iqvoc" / "admin" or "demo@iqvoc" / "cooluri" (cf. step #5)
59
59
  9. Visit the Users section and change the default passwords
60
60
 
61
+ ## Background Jobs
62
+
63
+ Note that some features like "Import" exposed in the Web UI store their workload
64
+ as jobs. You can either issue a job worker that runs continuously and watches
65
+ for new jobs via
66
+
67
+ ```
68
+ $ rake jobs:work
69
+ ```
70
+
71
+ or process jobs in a one-off way (in development or via cron):
72
+
73
+ ```
74
+ $ rake jobs:workoff
75
+ ```
76
+
61
77
  ## Compatibility
62
78
 
63
- iQvoc is fully compatible with Ruby 1.9, 2.0 and JRuby 1.7.
79
+ iQvoc is fully compatible with Ruby 1.9, 2.0, 2.1 and JRuby 1.7.
64
80
 
65
81
  ## Customization
66
82
 
@@ -0,0 +1,41 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
3
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
4
+ <svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="132.8px" height="51.1px" viewBox="0 0 132.8 51.1" enable-background="new 0 0 132.8 51.1" xml:space="preserve">
5
+ <g>
6
+ <g>
7
+ <path fill="#063D63" d="M63.4,50.5c0,0-1,0.6-3,0.6c-2.6,0-5-1.6-6.2-2.4c-1.6-1.1-4.2-2.6-6.9-3.4c-5.1,0-8.4-1.4-11-4.8
8
+ c-2.6-3.3-3.9-7.6-3.9-12.6c0-7.3,2.8-13.2,7.6-15.8c2-1.1,4.5-1.7,7-1.7c9,0,14.6,6.6,14.6,17.2c0,7.4-2.6,12.9-6.7,15.5
9
+ c0.9,0.3,2.2,1.2,3,1.7c1.2,0.7,2.7,1.4,4.2,1.4c1.5,0,3-0.2,3-0.2L63.4,50.5z M52.9,20c-0.5-1.7-2.3-4.3-5.9-4.3
10
+ c-2.3,0-4.3,1.2-5.3,3C40.6,20.9,40,23.9,40,28c0,5.8,1,9.5,3,11c1.1,0.8,2.4,1.2,4,1.2c4.7,0,6.7-3.6,6.7-12.2
11
+ C53.8,24.4,53.5,21.9,52.9,20z"/>
12
+ <path fill="#063D63" d="M77.6,44.8h-5.4l-9.1-24.5l6.7-0.8l3.6,11.2c0.5,1.7,1.3,4.4,1.6,6c0.2-1.5,0.7-3.8,1.5-6.3l3.4-10.5h6.8
13
+ L77.6,44.8z"/>
14
+ <path fill="#063D63" d="M106.8,41.8c-1.9,2.1-4.4,3.5-8.2,3.5c-6.8,0-11.2-5.1-11.2-12.9c0-7.8,4.5-13,11.2-13
15
+ c3.6,0,6.3,1.2,8.5,3.7c2,2.3,2.9,5.1,2.9,9.1C109.9,36.5,109,39.4,106.8,41.8z M101.5,25.5c-0.6-0.8-1.7-1.3-2.8-1.3
16
+ c-1.6,0-2.8,1-3.4,2.5c-0.4,1.3-0.7,3.1-0.7,5.6c0,2.9,0.3,5,0.8,6.3c0.6,1.4,2,2,3.3,2c2.8,0,4-2.5,4-8.4
17
+ C102.8,28.8,102.4,26.6,101.5,25.5z"/>
18
+ <path fill="#063D63" d="M130.5,43.8c-1.8,1-3.7,1.6-6.1,1.6c-6.9,0-10.9-4.8-10.9-12.9c0-5.7,2.2-9.5,5.1-11.5
19
+ c1.7-1.1,4.1-1.9,6.2-1.9c1.7,0,3.5,0.4,4.8,1.1c0.9,0.5,1.4,0.8,2.4,1.8l-3.2,4.3c-1.3-1.2-2.7-1.9-3.9-1.9
20
+ c-2.9,0-4.2,2.5-4.2,8.4c0,3.3,0.4,5.2,1.3,6.3c0.7,0.9,1.8,1.4,2.9,1.4c1.5,0,2.9-0.6,4.5-2l0.4-0.3l3,3.9
21
+ C131.9,42.9,131.4,43.2,130.5,43.8z"/>
22
+ </g>
23
+ <path fill="#5DAB31" d="M1.7,8.5l16.6-8.3c0,0,11.3,8.3,19.1-0.2c0,0-9.1,15.1-17.9,17L1.7,8.5z"/>
24
+ <g>
25
+ <path fill="#063D63" d="M21.6,45.7V21.2l6.5-3.7V43L21.6,45.7z"/>
26
+ </g>
27
+ <polygon fill="#EC6408" points="0,12.4 17.6,21.4 17.6,45.5 0,35.4"/>
28
+ </g>
29
+ <g>
30
+ </g>
31
+ <g>
32
+ </g>
33
+ <g>
34
+ </g>
35
+ <g>
36
+ </g>
37
+ <g>
38
+ </g>
39
+ <g>
40
+ </g>
41
+ </svg>
@@ -1,6 +1,6 @@
1
1
  //= require jquery-1.7.1
2
2
  //= require rails
3
- //= require jquery-ui-1.8.23.custom
3
+ //= require jquery-ui.custom
4
4
  //= require jquery-ui.datepicker-de
5
5
  //= require jquery.treeview
6
6
  //= require jquery.treeview.async
@@ -0,0 +1,62 @@
1
+ /*jslint vars: true, white: true */
2
+ /*global jQuery, IQVOC */
3
+
4
+ IQVOC.ConceptMapper = (function($) {
5
+
6
+ "use strict";
7
+
8
+ // `selector` is either a jQuery object, a DOM node or a string
9
+ function ConceptMapper(selector) {
10
+ if(arguments.length === 0) { // subclassing
11
+ return;
12
+ }
13
+
14
+ this.root = selector.jquery ? selector : $(selector);
15
+ this.matchTypes = this.determineMatchTypes();
16
+
17
+ var matchOptions = $.map(this.matchTypes, function(desc, id) {
18
+ return $("<option />").val(id).text(desc)[0];
19
+ });
20
+
21
+ // spawn UI elements
22
+
23
+ this.container = $("<div />").addClass("concept-mapper control-group");
24
+
25
+ this.input = $("<input />").attr("type", "text").prependTo(this.container);
26
+ $("<button />").addClass("btn fa fa-plus").insertAfter(this.input).
27
+ click($.proxy(this, "onConfirm"));
28
+ this.matchType = $("<select />").append(matchOptions).insertAfter(this.input);
29
+
30
+ this.container.prependTo(this.root);
31
+ }
32
+ ConceptMapper.prototype.delimiter = ", ";
33
+ ConceptMapper.prototype.onConfirm = function(ev) {
34
+ ev.preventDefault();
35
+
36
+ var textAreaName = this.matchType.val();
37
+ var textArea = document.getElementsByName(textAreaName)[0];
38
+ textArea = $(textArea);
39
+
40
+ var newURI = this.input.val();
41
+ var newValue = $.trim(textArea.val() + this.delimiter + newURI);
42
+
43
+ textArea.val(newValue);
44
+ this.input.val("");
45
+ this.root.trigger("concept-mapped", {
46
+ uri: newURI,
47
+ matchType: this.matchTypes[textAreaName]
48
+ });
49
+ };
50
+ ConceptMapper.prototype.determineMatchTypes = function() {
51
+ var data = {};
52
+ $("label", this.root).each(function(i, node) {
53
+ var el = $(node);
54
+ var fieldName = el.attr("for");
55
+ data[fieldName] = el.text();
56
+ });
57
+ return data;
58
+ };
59
+
60
+ return ConceptMapper;
61
+
62
+ }(jQuery));
@@ -0,0 +1,137 @@
1
+ /*jslint vars: true, white: true */
2
+ /*global jQuery, IQVOC */
3
+
4
+ IQVOC.ConceptMappingManager = (function($) {
5
+
6
+ "use strict";
7
+
8
+ function ConceptMappingManager(selector, editable) {
9
+ this.root = selector.jquery ? selector : $(selector);
10
+ this.editable = editable === true;
11
+ this.conceptMappings = this.determineConceptMappings();
12
+ this.datasets = $(document.body).data("datasets");
13
+
14
+ this.list = $('<ul class="concept-mappings" />').appendTo(this.root);
15
+ this.render();
16
+
17
+ this.root.on("concept-mapped", $.proxy(this, "onUpdate"));
18
+ }
19
+ ConceptMappingManager.prototype.onDelete = function(ev, instance) {
20
+ var btn = $(this);
21
+ var item = btn.closest("li");
22
+
23
+ var matchType = $(".concept-mapping-match-type", item).text();
24
+ var uri = $(".concept-mapping-link", item).attr("href");
25
+
26
+ var data = instance.conceptMappings[matchType];
27
+ var values = data.values;
28
+ // XXX: hacky
29
+ var hits = $.map(values, function(value, i) {
30
+ if(value.uri === uri) {
31
+ item.fadeOut(function() {
32
+ item.remove();
33
+ });
34
+ return i;
35
+ }
36
+ return null;
37
+ });
38
+ values.splice(hits[0], 1);
39
+
40
+ var uris = $.map(data.values, function(value) { return value.uri; });
41
+ data.el.val(uris.join(", "));
42
+ };
43
+ ConceptMappingManager.prototype.render = function() {
44
+ var self = this;
45
+
46
+ var items = [];
47
+ $.each(this.conceptMappings, function(label, category) {
48
+ $.each(category.values, function(i, item) {
49
+ item = self.renderBubble(item, label);
50
+ items.push(item[0]);
51
+ });
52
+ });
53
+
54
+ this.list.empty().append(items);
55
+ };
56
+ ConceptMappingManager.prototype.renderBubble = function(item, categoryLabel) {
57
+ var category = $('<span class="concept-mapping-match-type">').
58
+ text(categoryLabel);
59
+ var dataset = this.determineDataset(item.uri) || "";
60
+ dataset = $('<span class="concept-mapping-dataset" />').text(dataset.name);
61
+ if(this.editable) {
62
+ var self = this;
63
+ var btn = $("<i class='fa fa-times concept-mapping-remove' />").click(function(ev) {
64
+ // inject instance
65
+ var args = Array.prototype.slice.apply(arguments);
66
+ args.push(self);
67
+ // ev.preventDefault();
68
+ return self.onDelete.apply(this, args);
69
+ });
70
+ }
71
+ var link = $('<a class="concept-mapping-link unlabeled" />').attr("href", item.uri).
72
+ text(item.uri);
73
+ return $('<li class="concept-mapping" />').append(link).append(dataset).
74
+ append(category).append(btn);
75
+ };
76
+
77
+ // [{ el: jQuery Element, values: ["http://uri.de"], label: "Foo" }]
78
+ ConceptMappingManager.prototype.determineConceptMappings = function() {
79
+ return this.editable ? this.readFromTextArea() : this.readFromLinks();
80
+ };
81
+ ConceptMappingManager.prototype.readFromLinks = function() { // TODO: rename
82
+ var urisByMatchType = {};
83
+ $(".relation.panel", this.root).each(function(i, node) { // match-type panels
84
+ var container = $(node);
85
+ var matchType = container.find("h2").text();
86
+ var mappings = container.find(".entity_list a");
87
+ urisByMatchType[matchType] = {
88
+ values: $.map(mappings, function(node) {
89
+ return { uri: $(node).attr("href") };
90
+ })
91
+ };
92
+ }).remove(); // XXX: does not belong here
93
+ return urisByMatchType;
94
+ };
95
+ ConceptMappingManager.prototype.readFromTextArea = function() { // TODO: rename
96
+ var textAreas = this.root.find("textarea");
97
+
98
+ var labels = {};
99
+ this.root.find("label").each(function(i, node) {
100
+ var el = $(node);
101
+ labels[el.attr("for")] = el.text();
102
+ });
103
+
104
+ var urisByLabel = {};
105
+ textAreas.each(function(i, node) {
106
+ var el = $(node);
107
+ var label = labels[el.attr("name")];
108
+ var values = $.map($(node).val().split(","), function(item, i) {
109
+ item = $.trim(item);
110
+ return item ? { uri: item } : null;
111
+ });
112
+
113
+ urisByLabel[label] = { el: el, values: values };
114
+ }).closest("div:has(label)").hide(); // XXX: does not belong here
115
+
116
+ return urisByLabel;
117
+ };
118
+ ConceptMappingManager.prototype.onUpdate = function(ev, data) {
119
+ this.conceptMappings[data.matchType].values.push({ uri: data.uri });
120
+ this.render();
121
+
122
+ $(document.body).trigger("concept-label", this.list);
123
+ };
124
+ ConceptMappingManager.prototype.determineDataset = function(uri) {
125
+ var result = null;
126
+ $.each(this.datasets, function(url, name) {
127
+ if(uri.indexOf(url) === 0) {
128
+ result = { name: name, url: url };
129
+ return false;
130
+ }
131
+ });
132
+ return result;
133
+ };
134
+
135
+ return ConceptMappingManager;
136
+
137
+ }(jQuery));
@@ -11,6 +11,7 @@ var EntitySelector = function(node) {
11
11
  }
12
12
  this.el = $(node).hide(); // XXX: rename
13
13
  this.container = $('<div class="entity_select" />').data("widget", this);
14
+ this.indicator = $('<i class="spinner fa fa-refresh fa-spin hidden" />');
14
15
  this.delimiter = ",";
15
16
  this.singular = this.el.data("singular") || false;
16
17
  this.entities = this.getSelection();
@@ -23,26 +24,10 @@ var EntitySelector = function(node) {
23
24
  });
24
25
  selection = $('<ul class="entity_list" />').append(selection);
25
26
 
26
- var exclude = this.el.data("exclude") || null,
27
- img = $('<img class="spinner hidden" />').
28
- attr("src", "<%= asset_path('spinner_16x16.gif') %>");
29
27
  this.input = $("<input />").autocomplete({
30
28
  minLength: 3,
31
- source: function(req, callback) {
32
- var uri = self.el.data("query-url");
33
- $.getJSON(uri, { query: req.term }, function(data, status, xhr) { // TODO: error handling
34
- var excludes = self.getSelection().
35
- concat(exclude ? [exclude] : []);
36
- data = $.map(data, function(entity, i) {
37
- return $.inArray(entity.id, excludes) !== -1 ? null :
38
- { value: entity.id, label: entity.name };
39
- });
40
- self.input.autocomplete("option", "autoFocus", data.length === 1);
41
- callback(data);
42
- img.addClass("hidden");
43
- });
44
- },
45
- search: function(ev, ui) { img.removeClass("hidden"); },
29
+ source: $.proxy(this, "onInput"), // XXX: discards original `this` context
30
+ search: function(ev, ui) { self.indicator.removeClass("hidden"); },
46
31
  focus: function(ev, ui) { return false; },
47
32
  select: this.onSelect
48
33
  });
@@ -51,14 +36,55 @@ var EntitySelector = function(node) {
51
36
  // Bootstrap expects it to be there
52
37
  this.input.attr('type', 'text');
53
38
 
54
- this.container.append(this.input).append(img).append(selection).
39
+ this.container.append(this.input).append(this.indicator).append(selection).
55
40
  insertAfter(node).prepend(node);
56
41
 
57
42
  if(this.singular && this.entities.length) {
58
43
  this.input.hide();
59
44
  }
60
45
  };
46
+ // data transformations; target format is an array of objects with members
47
+ // `value` and `label`
48
+ // optional second argument `excludes` is an array of item IDs to exclude
49
+ EntitySelector.preprocessors = {
50
+ // converts an array of objects with members `id` and `name`
51
+ "default": function(data, excludes) {
52
+ return $.map(data, function(entity, i) {
53
+ return $.inArray(entity.id, excludes) !== -1 ? null :
54
+ { value: entity.id, label: entity.name };
55
+ });
56
+ }
57
+ };
58
+ EntitySelector.sourceSelectors = {
59
+ "default": function(callback) {
60
+ var uri = this.el.data("query-url");
61
+ callback(uri);
62
+ },
63
+ "dummy-prompt": function(callback) {
64
+ var uri = this.el.data("query-url");
65
+ var sources = { iQvoc: "hello", GEMET: "world" };
66
+ var params = $.map(sources, function(id, name) {
67
+ return confirm("include " + name) ? 'source=' + id : null;
68
+ }).join("&");
69
+ callback(uri + "?" + params);
70
+ }
71
+ };
61
72
  $.extend(EntitySelector.prototype, {
73
+ onInput: function(req, callback) {
74
+ var self = this;
75
+
76
+ var responder = function(data, status, xhr) { // TODO: rename, move elsewhere
77
+ data = self.processResponse(data);
78
+ self.input.autocomplete("option", "autoFocus", data.length === 1);
79
+ callback(data);
80
+ self.indicator.addClass("hidden");
81
+ };
82
+
83
+ var sourceSelector = this.el.data("source-selector") || "default";
84
+ EntitySelector.sourceSelectors[sourceSelector].call(this, function(uri) { // XXX: direct `EntitySelector` reference limits subclassing
85
+ $.getJSON(uri, { query: req.term }, responder); // TODO: error handling
86
+ });
87
+ },
62
88
  onSelect: function(ev, ui) {
63
89
  var el = $(this).val(""),
64
90
  widget = el.closest(".entity_select").data("widget");
@@ -83,6 +109,12 @@ $.extend(EntitySelector.prototype, {
83
109
  }
84
110
  ev.preventDefault();
85
111
  },
112
+ processResponse: function(data) { // TODO: rename
113
+ var preprocessor = this.el.data("preprocessor") || "default";
114
+ var exclude = this.el.data("exclude") || null;
115
+ var excludes = this.getSelection().concat(exclude ? [exclude] : []);
116
+ return EntitySelector.preprocessors[preprocessor](data, excludes); // XXX: direct `EntitySelector` reference limits subclassing
117
+ },
86
118
  createEntity: function(entity) {
87
119
  var el;
88
120
  if(this.uriTemplate) {
@@ -0,0 +1,75 @@
1
+ /*jslint vars: true, white: true */
2
+ /*global jQuery, IQVOC */
3
+
4
+ IQVOC.FederatedConceptMapper = (function($) {
5
+
6
+ "use strict";
7
+
8
+ var baseClass = IQVOC.ConceptMapper;
9
+
10
+ // `selector` is either a jQuery object, a DOM node or a string
11
+ function FederatedConceptMapper(selector) {
12
+ baseClass.apply(this, arguments);
13
+
14
+ var sources = this.root.data("datasets");
15
+ if(!sources) { // fall back to non-federated base class only
16
+ return;
17
+ }
18
+ sources["_custom"] = this.root.data("translation-other");
19
+
20
+ sources = $.map(sources, function(name, url) {
21
+ return $("<option />").val(url).text(name)[0];
22
+ });
23
+ this.source = $("<select />").append(sources).insertBefore(this.input);
24
+
25
+ this.indicator = $("<i />").
26
+ addClass("concept-mapper-indicator hidden fa fa-refresh fa-spin").
27
+ insertAfter(this.input);
28
+
29
+ var self = this;
30
+ this.input.autocomplete({ // TODO: extract autocomplete extension into subclass
31
+ source: $.proxy(this, "onChange"),
32
+ search: function(ev, ui) {
33
+ self.indicator.removeClass("hidden");
34
+ if(self.source.val() === "_custom") {
35
+ return false;
36
+ }
37
+ },
38
+ response: function(ev, ui) {
39
+ self.indicator.addClass("hidden");
40
+ },
41
+ minLength: 2
42
+ });
43
+ }
44
+ FederatedConceptMapper.prototype = new baseClass();
45
+ FederatedConceptMapper.prototype.onChange = function(req, callback) {
46
+ var self = this;
47
+ $.ajax({
48
+ type: "GET",
49
+ url: this.root.data("remote-proxy-url"),
50
+ data: {
51
+ prefix: encodeURIComponent(req.term), // FIXME: (double-)encoding should not be necessary
52
+ dataset: this.source.find("option:selected").text(),
53
+ layout: 0
54
+ },
55
+ success: function() {
56
+ // inject callback
57
+ var args = Array.prototype.slice.apply(arguments);
58
+ args.push(callback);
59
+ return self.onResults.apply(self, args);
60
+ }
61
+ });
62
+ };
63
+ FederatedConceptMapper.prototype.onResults = function(html, status, xhr, callback) {
64
+ var doc = $("<div />").append(html);
65
+ var concepts = doc.find(".concept-item-link");
66
+ var items = $.map(concepts, function(node, i) {
67
+ var el = $(node);
68
+ return { label: el.text(), value: el.data("resource-url") };
69
+ });
70
+ callback(items);
71
+ };
72
+
73
+ return FederatedConceptMapper;
74
+
75
+ }(jQuery));