sufia 0.0.1 → 0.0.2

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 (50) hide show
  1. data/Gemfile +1 -1
  2. data/app/assets/javascripts/sufia.js +6 -5
  3. data/app/assets/javascripts/terms_of_service.js +28 -0
  4. data/app/controllers/batch_edits_controller.rb +2 -2
  5. data/app/controllers/generic_files_controller.rb +2 -2
  6. data/app/models/datastreams/fits_datastream.rb +1 -1
  7. data/app/views/batch/edit.html.erb +6 -6
  8. data/app/views/batch_edits/edit.html.erb +10 -6
  9. data/app/views/catalog/_search_form.html.erb +2 -2
  10. data/app/views/dashboard/index.html.erb +7 -7
  11. data/app/views/generic_files/_asset_updated_flash.html.erb +1 -1
  12. data/app/views/generic_files/_descriptions.html.erb +1 -1
  13. data/app/views/generic_files/_versioning.html.erb +1 -1
  14. data/app/views/generic_files/edit.html.erb +10 -10
  15. data/app/views/generic_files/new.html.erb +3 -3
  16. data/app/views/generic_files/show.html.erb +1 -1
  17. data/app/views/users/edit.html.erb +3 -3
  18. data/config/locales/sufia.en.yml +10 -3
  19. data/features/step_definitions/scholarsphere.rb +3 -1
  20. data/features/support/paths.rb +1 -1
  21. data/lib/sufia/generic_file.rb +2 -3
  22. data/lib/sufia/generic_file/characterization.rb +1 -1
  23. data/lib/sufia/jobs/batch_update_job.rb +2 -2
  24. data/lib/sufia/version.rb +1 -1
  25. data/lib/tasks/resque.rake +27 -1
  26. data/spec/models/file_content_datastream_spec.rb +2 -2
  27. data/spec/models/generic_file_spec.rb +7 -4
  28. data/sufia.gemspec +3 -5
  29. data/vendor/assets/javascripts/fileupload.js +1 -3
  30. data/vendor/assets/javascripts/fileupload/application.js +0 -25
  31. data/vendor/assets/javascripts/fileupload/tmpl.js +86 -0
  32. data/vendor/assets/javascripts/jquery-ui-1.9.2/jquery.ui.autocomplete.js +602 -0
  33. data/vendor/assets/javascripts/jquery-ui-1.9.2/jquery.ui.core.js +356 -0
  34. data/vendor/assets/javascripts/jquery-ui-1.9.2/jquery.ui.menu.js +610 -0
  35. data/vendor/assets/javascripts/jquery-ui-1.9.2/jquery.ui.widget.js +528 -0
  36. data/vendor/assets/javascripts/swfobject.js +4 -0
  37. metadata +15 -37
  38. data/app/assets/javascripts/generic_files.js +0 -17
  39. data/tasks/resque.rake +0 -27
  40. data/vendor/assets/javascripts/blacklight.js +0 -513
  41. data/vendor/assets/javascripts/bootstrap-collapse.js +0 -157
  42. data/vendor/assets/javascripts/bootstrap-popover.js +0 -98
  43. data/vendor/assets/javascripts/bootstrap-tooltip.js +0 -275
  44. data/vendor/assets/javascripts/fileupload/tmpl.min.js +0 -1
  45. data/vendor/assets/javascripts/jquery-1.7.2.min.js +0 -4
  46. data/vendor/assets/javascripts/jquery-1.8.2.js +0 -9440
  47. data/vendor/assets/javascripts/jquery-1.8.2.min.js +0 -2
  48. data/vendor/assets/javascripts/jquery-ui-1.8.16.custom.min.js +0 -791
  49. data/vendor/assets/javascripts/jquery-ui-1.8.23.custom.min.js +0 -125
  50. data/vendor/assets/javascripts/jquery_ujs.js +0 -377
@@ -106,9 +106,9 @@ describe FileContentDatastream do
106
106
  @generic_file.delete
107
107
  end
108
108
  it "should only return true when the datastream has actually changed" do
109
- @generic_file.add_file_datastream(File.new(fixture_path + '/world.png'), :dsid=>'content')
109
+ @generic_file.add_file_datastream(File.new(fixture_path + '/world.png', 'rb'), :dsid=>'content')
110
110
  @generic_file.content.changed?.should be_true
111
- @generic_file.save
111
+ @generic_file.save!
112
112
  @generic_file.content.changed?.should be_false
113
113
 
114
114
  # Add a thumbnail ds
@@ -130,9 +130,10 @@ describe GenericFile do
130
130
  @file.to_param.should == @file.noid
131
131
  end
132
132
  describe "that have been saved" do
133
- before(:each) do
134
- Sufia.queue.should_receive(:push).once
135
- end
133
+ # This file has no content, so it doesn't characterize
134
+ # before(:each) do
135
+ # Sufia.queue.should_receive(:push).once
136
+ # end
136
137
  after(:each) do
137
138
  unless @file.inner_object.class == ActiveFedora::UnsavedDigitalObject
138
139
  begin
@@ -194,6 +195,7 @@ describe GenericFile do
194
195
  @file.identifier = "urn:isbn:1234567890"
195
196
  @file.based_near = "Medina, Saudi Arabia"
196
197
  @file.related_url = "http://example.org/TheWork/"
198
+ @file.mime_type = "image/jpeg"
197
199
  local = @file.to_solr
198
200
  local.should_not be_nil
199
201
  local["generic_file__part_of_t"].should be_nil
@@ -213,6 +215,7 @@ describe GenericFile do
213
215
  local["generic_file__format_t"].should == ["application/pdf"]
214
216
  local["generic_file__identifier_t"].should == ["urn:isbn:1234567890"]
215
217
  local["generic_file__based_near_t"].should == ["Medina, Saudi Arabia"]
218
+ local["mime_type_t"].should == ["image/jpeg"]
216
219
  end
217
220
  it "should support multi-valued fields in solr" do
218
221
  @file.tag = ["tag1", "tag2"]
@@ -228,7 +231,7 @@ describe GenericFile do
228
231
  before do
229
232
  @f = GenericFile.new
230
233
  @f.stub(:mime_type=>'image/png', :width=>['50'], :height=>['50']) #Would get set by the characterization job
231
- @f.add_file_datastream(File.new("#{fixture_path}/world.png"), :dsid=>'content')
234
+ @f.add_file_datastream(File.new("#{fixture_path}/world.png", 'rb'), :dsid=>'content')
232
235
  @f.apply_depositor_metadata('mjg36')
233
236
  @f.save
234
237
  @mock_image = mock("image", :from_blob=>true)
data/sufia.gemspec CHANGED
@@ -18,15 +18,13 @@ Gem::Specification.new do |gem|
18
18
  gem.add_dependency 'rails', '~> 3.2.8'
19
19
  gem.add_dependency 'blacklight', '~> 4.0.0'
20
20
  gem.add_dependency 'blacklight_advanced_search'
21
- gem.add_dependency "hydra-head", "~> 5.0.0"
22
- gem.add_dependency "active-fedora", "~> 5.0.0"
21
+ gem.add_dependency "hydra-head", "~> 5.1"
23
22
 
24
- #TODO remove
25
23
  gem.add_dependency 'noid', '0.5.5'
26
- gem.add_dependency 'hydra-batch-edit', '~> 0.0.7'
24
+ gem.add_dependency 'hydra-batch-edit', '~> 0.1.0'
27
25
 
28
26
  # Other components
29
- gem.add_dependency 'resque', '1.23.0'#, :require => 'resque/server'
27
+ gem.add_dependency 'resque', '~> 1.23.0'#, :require => 'resque/server'
30
28
  gem.add_dependency 'resque-pool', '0.3.0'
31
29
  # NOTE: the :require arg is necessary on Linux-based hosts
32
30
  gem.add_dependency 'rmagick', '2.13.1'#, :require => 'RMagick'
@@ -1,6 +1,4 @@
1
- //= require jquery-ui-1.8.16.custom.min
2
- //= require jquery_ujs
3
- //= require fileupload/tmpl.min
1
+ //= require fileupload/tmpl
4
2
  //= require fileupload/jquery.iframe-transport
5
3
  //= require fileupload/jquery.fileupload.js
6
4
  //= require fileupload/jquery.fileupload-ui.js
@@ -25,32 +25,7 @@ var max_total_file_size_str = "500 MB";
25
25
  var first_file_after_max = '';
26
26
 
27
27
  $(function () {
28
- $('#main_upload_start').attr('disabled', true);
29
28
  'use strict';
30
- $("#upload_tooltip").hide();
31
- $("#main_upload_start_span").mousemove(function(e){
32
- if ( !$('#terms_of_service').is(':checked') ){
33
- $('#main_upload_start').attr('disabled', true);
34
- $("#upload_tooltip").show();
35
- $("#upload_tooltip").css({
36
- top: (e.clientY+5)+ "px",
37
- left: (e.clientX+5) + "px"
38
- });
39
- } else {
40
- if (filestoupload > 0) $('#main_upload_start').attr('disabled', false);
41
- $("#upload_tooltip").hide();
42
- }
43
- });
44
- $("#main_upload_start_span").mouseout(function(e){
45
- $("#upload_tooltip").hide();
46
- });
47
- $("#main_upload_start_span").mouseleave(function(e){
48
- $("#upload_tooltip").hide();
49
- });
50
- $('#terms_of_service').click(function () {
51
- $('#main_upload_start').attr('disabled', !((this.checked) && (filestoupload > 0)));
52
- $("#upload_tooltip").hide();
53
- });
54
29
 
55
30
  // Initialize the jQuery File Upload widget:
56
31
  $('#fileupload').fileupload();
@@ -0,0 +1,86 @@
1
+ /*
2
+ * JavaScript Templates 2.1.0
3
+ * https://github.com/blueimp/JavaScript-Templates
4
+ *
5
+ * Copyright 2011, Sebastian Tschan
6
+ * https://blueimp.net
7
+ *
8
+ * Licensed under the MIT license:
9
+ * http://www.opensource.org/licenses/MIT
10
+ *
11
+ * Inspired by John Resig's JavaScript Micro-Templating:
12
+ * http://ejohn.org/blog/javascript-micro-templating/
13
+ */
14
+
15
+ /*jslint evil: true, regexp: true */
16
+ /*global document, define */
17
+
18
+ (function ($) {
19
+ "use strict";
20
+ var tmpl = function (str, data) {
21
+ var f = !/[^\w\-\.:]/.test(str) ? tmpl.cache[str] = tmpl.cache[str] ||
22
+ tmpl(tmpl.load(str)) :
23
+ new Function(
24
+ tmpl.arg + ',tmpl',
25
+ "var _e=tmpl.encode" + tmpl.helper + ",_s='" +
26
+ str.replace(tmpl.regexp, tmpl.func) +
27
+ "';return _s;"
28
+ );
29
+ return data ? f(data, tmpl) : function (data) {
30
+ return f(data, tmpl);
31
+ };
32
+ };
33
+ tmpl.cache = {};
34
+ tmpl.load = function (id) {
35
+ return document.getElementById(id).innerHTML;
36
+ };
37
+ tmpl.regexp = /([\s'\\])(?![^%]*%\})|(?:\{%(=|#)([\s\S]+?)%\})|(\{%)|(%\})/g;
38
+ tmpl.func = function (s, p1, p2, p3, p4, p5) {
39
+ if (p1) { // whitespace, quote and backspace in interpolation context
40
+ return {
41
+ "\n": "\\n",
42
+ "\r": "\\r",
43
+ "\t": "\\t",
44
+ " " : " "
45
+ }[s] || "\\" + s;
46
+ }
47
+ if (p2) { // interpolation: {%=prop%}, or unescaped: {%#prop%}
48
+ if (p2 === "=") {
49
+ return "'+_e(" + p3 + ")+'";
50
+ }
51
+ return "'+(" + p3 + "||'')+'";
52
+ }
53
+ if (p4) { // evaluation start tag: {%
54
+ return "';";
55
+ }
56
+ if (p5) { // evaluation end tag: %}
57
+ return "_s+='";
58
+ }
59
+ };
60
+ tmpl.encReg = /[<>&"'\x00]/g;
61
+ tmpl.encMap = {
62
+ "<" : "&lt;",
63
+ ">" : "&gt;",
64
+ "&" : "&amp;",
65
+ "\"" : "&quot;",
66
+ "'" : "&#39;"
67
+ };
68
+ tmpl.encode = function (s) {
69
+ return String(s || "").replace(
70
+ tmpl.encReg,
71
+ function (c) {
72
+ return tmpl.encMap[c] || "";
73
+ }
74
+ );
75
+ };
76
+ tmpl.arg = "o";
77
+ tmpl.helper = ",print=function(s,e){_s+=e&&(s||'')||_e(s);}" +
78
+ ",include=function(s,d){_s+=tmpl(s,d);}";
79
+ if (typeof define === "function" && define.amd) {
80
+ define(function () {
81
+ return tmpl;
82
+ });
83
+ } else {
84
+ $.tmpl = tmpl;
85
+ }
86
+ }(this));
@@ -0,0 +1,602 @@
1
+ /*!
2
+ * jQuery UI Autocomplete @VERSION
3
+ * http://jqueryui.com
4
+ *
5
+ * Copyright 2012 jQuery Foundation and other contributors
6
+ * Released under the MIT license.
7
+ * http://jquery.org/license
8
+ *
9
+ * http://api.jqueryui.com/autocomplete/
10
+ *
11
+ * Depends:
12
+ * jquery.ui.core.js
13
+ * jquery.ui.widget.js
14
+ * jquery.ui.position.js
15
+ * jquery.ui.menu.js
16
+ */
17
+ (function( $, undefined ) {
18
+
19
+ // used to prevent race conditions with remote data sources
20
+ var requestIndex = 0;
21
+
22
+ $.widget( "ui.autocomplete", {
23
+ version: "@VERSION",
24
+ defaultElement: "<input>",
25
+ options: {
26
+ appendTo: "body",
27
+ autoFocus: false,
28
+ delay: 300,
29
+ minLength: 1,
30
+ position: {
31
+ my: "left top",
32
+ at: "left bottom",
33
+ collision: "none"
34
+ },
35
+ source: null,
36
+
37
+ // callbacks
38
+ change: null,
39
+ close: null,
40
+ focus: null,
41
+ open: null,
42
+ response: null,
43
+ search: null,
44
+ select: null
45
+ },
46
+
47
+ pending: 0,
48
+
49
+ _create: function() {
50
+ // Some browsers only repeat keydown events, not keypress events,
51
+ // so we use the suppressKeyPress flag to determine if we've already
52
+ // handled the keydown event. #7269
53
+ // Unfortunately the code for & in keypress is the same as the up arrow,
54
+ // so we use the suppressKeyPressRepeat flag to avoid handling keypress
55
+ // events when we know the keydown event was used to modify the
56
+ // search term. #7799
57
+ var suppressKeyPress, suppressKeyPressRepeat, suppressInput;
58
+
59
+ this.isMultiLine = this._isMultiLine();
60
+ this.valueMethod = this.element[ this.element.is( "input,textarea" ) ? "val" : "text" ];
61
+ this.isNewMenu = true;
62
+
63
+ this.element
64
+ .addClass( "ui-autocomplete-input" )
65
+ .attr( "autocomplete", "off" );
66
+
67
+ this._on( this.element, {
68
+ keydown: function( event ) {
69
+ if ( this.element.prop( "readOnly" ) ) {
70
+ suppressKeyPress = true;
71
+ suppressInput = true;
72
+ suppressKeyPressRepeat = true;
73
+ return;
74
+ }
75
+
76
+ suppressKeyPress = false;
77
+ suppressInput = false;
78
+ suppressKeyPressRepeat = false;
79
+ var keyCode = $.ui.keyCode;
80
+ switch( event.keyCode ) {
81
+ case keyCode.PAGE_UP:
82
+ suppressKeyPress = true;
83
+ this._move( "previousPage", event );
84
+ break;
85
+ case keyCode.PAGE_DOWN:
86
+ suppressKeyPress = true;
87
+ this._move( "nextPage", event );
88
+ break;
89
+ case keyCode.UP:
90
+ suppressKeyPress = true;
91
+ this._keyEvent( "previous", event );
92
+ break;
93
+ case keyCode.DOWN:
94
+ suppressKeyPress = true;
95
+ this._keyEvent( "next", event );
96
+ break;
97
+ case keyCode.ENTER:
98
+ case keyCode.NUMPAD_ENTER:
99
+ // when menu is open and has focus
100
+ if ( this.menu.active ) {
101
+ // #6055 - Opera still allows the keypress to occur
102
+ // which causes forms to submit
103
+ suppressKeyPress = true;
104
+ event.preventDefault();
105
+ this.menu.select( event );
106
+ }
107
+ break;
108
+ case keyCode.TAB:
109
+ if ( this.menu.active ) {
110
+ this.menu.select( event );
111
+ }
112
+ break;
113
+ case keyCode.ESCAPE:
114
+ if ( this.menu.element.is( ":visible" ) ) {
115
+ this._value( this.term );
116
+ this.close( event );
117
+ // Different browsers have different default behavior for escape
118
+ // Single press can mean undo or clear
119
+ // Double press in IE means clear the whole form
120
+ event.preventDefault();
121
+ }
122
+ break;
123
+ default:
124
+ suppressKeyPressRepeat = true;
125
+ // search timeout should be triggered before the input value is changed
126
+ this._searchTimeout( event );
127
+ break;
128
+ }
129
+ },
130
+ keypress: function( event ) {
131
+ if ( suppressKeyPress ) {
132
+ suppressKeyPress = false;
133
+ event.preventDefault();
134
+ return;
135
+ }
136
+ if ( suppressKeyPressRepeat ) {
137
+ return;
138
+ }
139
+
140
+ // replicate some key handlers to allow them to repeat in Firefox and Opera
141
+ var keyCode = $.ui.keyCode;
142
+ switch( event.keyCode ) {
143
+ case keyCode.PAGE_UP:
144
+ this._move( "previousPage", event );
145
+ break;
146
+ case keyCode.PAGE_DOWN:
147
+ this._move( "nextPage", event );
148
+ break;
149
+ case keyCode.UP:
150
+ this._keyEvent( "previous", event );
151
+ break;
152
+ case keyCode.DOWN:
153
+ this._keyEvent( "next", event );
154
+ break;
155
+ }
156
+ },
157
+ input: function( event ) {
158
+ if ( suppressInput ) {
159
+ suppressInput = false;
160
+ event.preventDefault();
161
+ return;
162
+ }
163
+ this._searchTimeout( event );
164
+ },
165
+ focus: function() {
166
+ this.selectedItem = null;
167
+ this.previous = this._value();
168
+ },
169
+ blur: function( event ) {
170
+ if ( this.cancelBlur ) {
171
+ delete this.cancelBlur;
172
+ return;
173
+ }
174
+
175
+ clearTimeout( this.searching );
176
+ this.close( event );
177
+ this._change( event );
178
+ }
179
+ });
180
+
181
+ this._initSource();
182
+ this.menu = $( "<ul>" )
183
+ .addClass( "ui-autocomplete" )
184
+ .appendTo( this.document.find( this.options.appendTo || "body" )[ 0 ] )
185
+ .menu({
186
+ // custom key handling for now
187
+ input: $(),
188
+ // disable ARIA support, the live region takes care of that
189
+ role: null
190
+ })
191
+ .zIndex( this.element.zIndex() + 1 )
192
+ .hide()
193
+ .data( "menu" );
194
+
195
+ this._on( this.menu.element, {
196
+ mousedown: function( event ) {
197
+ // prevent moving focus out of the text field
198
+ event.preventDefault();
199
+
200
+ // IE doesn't prevent moving focus even with event.preventDefault()
201
+ // so we set a flag to know when we should ignore the blur event
202
+ this.cancelBlur = true;
203
+ this._delay(function() {
204
+ delete this.cancelBlur;
205
+ });
206
+
207
+ // clicking on the scrollbar causes focus to shift to the body
208
+ // but we can't detect a mouseup or a click immediately afterward
209
+ // so we have to track the next mousedown and close the menu if
210
+ // the user clicks somewhere outside of the autocomplete
211
+ var menuElement = this.menu.element[ 0 ];
212
+ if ( !$( event.target ).closest( ".ui-menu-item" ).length ) {
213
+ this._delay(function() {
214
+ var that = this;
215
+ this.document.one( "mousedown", function( event ) {
216
+ if ( event.target !== that.element[ 0 ] &&
217
+ event.target !== menuElement &&
218
+ !$.contains( menuElement, event.target ) ) {
219
+ that.close();
220
+ }
221
+ });
222
+ });
223
+ }
224
+ },
225
+ menufocus: function( event, ui ) {
226
+ // #7024 - Prevent accidental activation of menu items in Firefox
227
+ if ( this.isNewMenu ) {
228
+ this.isNewMenu = false;
229
+ if ( event.originalEvent && /^mouse/.test( event.originalEvent.type ) ) {
230
+ this.menu.blur();
231
+
232
+ this.document.one( "mousemove", function() {
233
+ $( event.target ).trigger( event.originalEvent );
234
+ });
235
+
236
+ return;
237
+ }
238
+ }
239
+
240
+ // back compat for _renderItem using item.autocomplete, via #7810
241
+ // TODO remove the fallback, see #8156
242
+ var item = ui.item.data( "ui-autocomplete-item" ) || ui.item.data( "item.autocomplete" );
243
+ if ( false !== this._trigger( "focus", event, { item: item } ) ) {
244
+ // use value to match what will end up in the input, if it was a key event
245
+ if ( event.originalEvent && /^key/.test( event.originalEvent.type ) ) {
246
+ this._value( item.value );
247
+ }
248
+ } else {
249
+ // Normally the input is populated with the item's value as the
250
+ // menu is navigated, causing screen readers to notice a change and
251
+ // announce the item. Since the focus event was canceled, this doesn't
252
+ // happen, so we update the live region so that screen readers can
253
+ // still notice the change and announce it.
254
+ this.liveRegion.text( item.value );
255
+ }
256
+ },
257
+ menuselect: function( event, ui ) {
258
+ // back compat for _renderItem using item.autocomplete, via #7810
259
+ // TODO remove the fallback, see #8156
260
+ var item = ui.item.data( "ui-autocomplete-item" ) || ui.item.data( "item.autocomplete" ),
261
+ previous = this.previous;
262
+
263
+ // only trigger when focus was lost (click on menu)
264
+ if ( this.element[0] !== this.document[0].activeElement ) {
265
+ this.element.focus();
266
+ this.previous = previous;
267
+ // #6109 - IE triggers two focus events and the second
268
+ // is asynchronous, so we need to reset the previous
269
+ // term synchronously and asynchronously :-(
270
+ this._delay(function() {
271
+ this.previous = previous;
272
+ this.selectedItem = item;
273
+ });
274
+ }
275
+
276
+ if ( false !== this._trigger( "select", event, { item: item } ) ) {
277
+ this._value( item.value );
278
+ }
279
+ // reset the term after the select event
280
+ // this allows custom select handling to work properly
281
+ this.term = this._value();
282
+
283
+ this.close( event );
284
+ this.selectedItem = item;
285
+ }
286
+ });
287
+
288
+ this.liveRegion = $( "<span>", {
289
+ role: "status",
290
+ "aria-live": "polite"
291
+ })
292
+ .addClass( "ui-helper-hidden-accessible" )
293
+ .insertAfter( this.element );
294
+
295
+ if ( $.fn.bgiframe ) {
296
+ this.menu.element.bgiframe();
297
+ }
298
+
299
+ // turning off autocomplete prevents the browser from remembering the
300
+ // value when navigating through history, so we re-enable autocomplete
301
+ // if the page is unloaded before the widget is destroyed. #7790
302
+ this._on( this.window, {
303
+ beforeunload: function() {
304
+ this.element.removeAttr( "autocomplete" );
305
+ }
306
+ });
307
+ },
308
+
309
+ _destroy: function() {
310
+ clearTimeout( this.searching );
311
+ this.element
312
+ .removeClass( "ui-autocomplete-input" )
313
+ .removeAttr( "autocomplete" );
314
+ this.menu.element.remove();
315
+ this.liveRegion.remove();
316
+ },
317
+
318
+ _setOption: function( key, value ) {
319
+ this._super( key, value );
320
+ if ( key === "source" ) {
321
+ this._initSource();
322
+ }
323
+ if ( key === "appendTo" ) {
324
+ this.menu.element.appendTo( this.document.find( value || "body" )[0] );
325
+ }
326
+ if ( key === "disabled" && value && this.xhr ) {
327
+ this.xhr.abort();
328
+ }
329
+ },
330
+
331
+ _isMultiLine: function() {
332
+ // Textareas are always multi-line
333
+ if ( this.element.is( "textarea" ) ) {
334
+ return true;
335
+ }
336
+ // Inputs are always single-line, even if inside a contentEditable element
337
+ // IE also treats inputs as contentEditable
338
+ if ( this.element.is( "input" ) ) {
339
+ return false;
340
+ }
341
+ // All other element types are determined by whether or not they're contentEditable
342
+ return this.element.prop( "isContentEditable" );
343
+ },
344
+
345
+ _initSource: function() {
346
+ var array, url,
347
+ that = this;
348
+ if ( $.isArray(this.options.source) ) {
349
+ array = this.options.source;
350
+ this.source = function( request, response ) {
351
+ response( $.ui.autocomplete.filter( array, request.term ) );
352
+ };
353
+ } else if ( typeof this.options.source === "string" ) {
354
+ url = this.options.source;
355
+ this.source = function( request, response ) {
356
+ if ( that.xhr ) {
357
+ that.xhr.abort();
358
+ }
359
+ that.xhr = $.ajax({
360
+ url: url,
361
+ data: request,
362
+ dataType: "json",
363
+ success: function( data ) {
364
+ response( data );
365
+ },
366
+ error: function() {
367
+ response( [] );
368
+ }
369
+ });
370
+ };
371
+ } else {
372
+ this.source = this.options.source;
373
+ }
374
+ },
375
+
376
+ _searchTimeout: function( event ) {
377
+ clearTimeout( this.searching );
378
+ this.searching = this._delay(function() {
379
+ // only search if the value has changed
380
+ if ( this.term !== this._value() ) {
381
+ this.selectedItem = null;
382
+ this.search( null, event );
383
+ }
384
+ }, this.options.delay );
385
+ },
386
+
387
+ search: function( value, event ) {
388
+ value = value != null ? value : this._value();
389
+
390
+ // always save the actual value, not the one passed as an argument
391
+ this.term = this._value();
392
+
393
+ if ( value.length < this.options.minLength ) {
394
+ return this.close( event );
395
+ }
396
+
397
+ if ( this._trigger( "search", event ) === false ) {
398
+ return;
399
+ }
400
+
401
+ return this._search( value );
402
+ },
403
+
404
+ _search: function( value ) {
405
+ this.pending++;
406
+ this.element.addClass( "ui-autocomplete-loading" );
407
+ this.cancelSearch = false;
408
+
409
+ this.source( { term: value }, this._response() );
410
+ },
411
+
412
+ _response: function() {
413
+ var that = this,
414
+ index = ++requestIndex;
415
+
416
+ return function( content ) {
417
+ if ( index === requestIndex ) {
418
+ that.__response( content );
419
+ }
420
+
421
+ that.pending--;
422
+ if ( !that.pending ) {
423
+ that.element.removeClass( "ui-autocomplete-loading" );
424
+ }
425
+ };
426
+ },
427
+
428
+ __response: function( content ) {
429
+ if ( content ) {
430
+ content = this._normalize( content );
431
+ }
432
+ this._trigger( "response", null, { content: content } );
433
+ if ( !this.options.disabled && content && content.length && !this.cancelSearch ) {
434
+ this._suggest( content );
435
+ this._trigger( "open" );
436
+ } else {
437
+ // use ._close() instead of .close() so we don't cancel future searches
438
+ this._close();
439
+ }
440
+ },
441
+
442
+ close: function( event ) {
443
+ this.cancelSearch = true;
444
+ this._close( event );
445
+ },
446
+
447
+ _close: function( event ) {
448
+ if ( this.menu.element.is( ":visible" ) ) {
449
+ this.menu.element.hide();
450
+ this.menu.blur();
451
+ this.isNewMenu = true;
452
+ this._trigger( "close", event );
453
+ }
454
+ },
455
+
456
+ _change: function( event ) {
457
+ if ( this.previous !== this._value() ) {
458
+ this._trigger( "change", event, { item: this.selectedItem } );
459
+ }
460
+ },
461
+
462
+ _normalize: function( items ) {
463
+ // assume all items have the right format when the first item is complete
464
+ if ( items.length && items[0].label && items[0].value ) {
465
+ return items;
466
+ }
467
+ return $.map( items, function( item ) {
468
+ if ( typeof item === "string" ) {
469
+ return {
470
+ label: item,
471
+ value: item
472
+ };
473
+ }
474
+ return $.extend({
475
+ label: item.label || item.value,
476
+ value: item.value || item.label
477
+ }, item );
478
+ });
479
+ },
480
+
481
+ _suggest: function( items ) {
482
+ var ul = this.menu.element
483
+ .empty()
484
+ .zIndex( this.element.zIndex() + 1 );
485
+ this._renderMenu( ul, items );
486
+ this.menu.refresh();
487
+
488
+ // size and position menu
489
+ ul.show();
490
+ this._resizeMenu();
491
+ ul.position( $.extend({
492
+ of: this.element
493
+ }, this.options.position ));
494
+
495
+ if ( this.options.autoFocus ) {
496
+ this.menu.next();
497
+ }
498
+ },
499
+
500
+ _resizeMenu: function() {
501
+ var ul = this.menu.element;
502
+ ul.outerWidth( Math.max(
503
+ // Firefox wraps long text (possibly a rounding bug)
504
+ // so we add 1px to avoid the wrapping (#7513)
505
+ ul.width( "" ).outerWidth() + 1,
506
+ this.element.outerWidth()
507
+ ) );
508
+ },
509
+
510
+ _renderMenu: function( ul, items ) {
511
+ var that = this;
512
+ $.each( items, function( index, item ) {
513
+ that._renderItemData( ul, item );
514
+ });
515
+ },
516
+
517
+ _renderItemData: function( ul, item ) {
518
+ return this._renderItem( ul, item ).data( "ui-autocomplete-item", item );
519
+ },
520
+
521
+ _renderItem: function( ul, item ) {
522
+ return $( "<li>" )
523
+ .append( $( "<a>" ).text( item.label ) )
524
+ .appendTo( ul );
525
+ },
526
+
527
+ _move: function( direction, event ) {
528
+ if ( !this.menu.element.is( ":visible" ) ) {
529
+ this.search( null, event );
530
+ return;
531
+ }
532
+ if ( this.menu.isFirstItem() && /^previous/.test( direction ) ||
533
+ this.menu.isLastItem() && /^next/.test( direction ) ) {
534
+ this._value( this.term );
535
+ this.menu.blur();
536
+ return;
537
+ }
538
+ this.menu[ direction ]( event );
539
+ },
540
+
541
+ widget: function() {
542
+ return this.menu.element;
543
+ },
544
+
545
+ _value: function() {
546
+ return this.valueMethod.apply( this.element, arguments );
547
+ },
548
+
549
+ _keyEvent: function( keyEvent, event ) {
550
+ if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) {
551
+ this._move( keyEvent, event );
552
+
553
+ // prevents moving cursor to beginning/end of the text field in some browsers
554
+ event.preventDefault();
555
+ }
556
+ }
557
+ });
558
+
559
+ $.extend( $.ui.autocomplete, {
560
+ escapeRegex: function( value ) {
561
+ return value.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&");
562
+ },
563
+ filter: function(array, term) {
564
+ var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" );
565
+ return $.grep( array, function(value) {
566
+ return matcher.test( value.label || value.value || value );
567
+ });
568
+ }
569
+ });
570
+
571
+
572
+ // live region extension, adding a `messages` option
573
+ // NOTE: This is an experimental API. We are still investigating
574
+ // a full solution for string manipulation and internationalization.
575
+ $.widget( "ui.autocomplete", $.ui.autocomplete, {
576
+ options: {
577
+ messages: {
578
+ noResults: "No search results.",
579
+ results: function( amount ) {
580
+ return amount + ( amount > 1 ? " results are" : " result is" ) +
581
+ " available, use up and down arrow keys to navigate.";
582
+ }
583
+ }
584
+ },
585
+
586
+ __response: function( content ) {
587
+ var message;
588
+ this._superApply( arguments );
589
+ if ( this.options.disabled || this.cancelSearch ) {
590
+ return;
591
+ }
592
+ if ( content && content.length ) {
593
+ message = this.options.messages.results( content.length );
594
+ } else {
595
+ message = this.options.messages.noResults;
596
+ }
597
+ this.liveRegion.text( message );
598
+ }
599
+ });
600
+
601
+
602
+ }( jQuery ));