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
@@ -0,0 +1,356 @@
1
+ /*!
2
+ * jQuery UI Core @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/category/ui-core/
10
+ */
11
+ (function( $, undefined ) {
12
+
13
+ var uuid = 0,
14
+ runiqueId = /^ui-id-\d+$/;
15
+
16
+ // prevent duplicate loading
17
+ // this is only a problem because we proxy existing functions
18
+ // and we don't want to double proxy them
19
+ $.ui = $.ui || {};
20
+ if ( $.ui.version ) {
21
+ return;
22
+ }
23
+
24
+ $.extend( $.ui, {
25
+ version: "@VERSION",
26
+
27
+ keyCode: {
28
+ BACKSPACE: 8,
29
+ COMMA: 188,
30
+ DELETE: 46,
31
+ DOWN: 40,
32
+ END: 35,
33
+ ENTER: 13,
34
+ ESCAPE: 27,
35
+ HOME: 36,
36
+ LEFT: 37,
37
+ NUMPAD_ADD: 107,
38
+ NUMPAD_DECIMAL: 110,
39
+ NUMPAD_DIVIDE: 111,
40
+ NUMPAD_ENTER: 108,
41
+ NUMPAD_MULTIPLY: 106,
42
+ NUMPAD_SUBTRACT: 109,
43
+ PAGE_DOWN: 34,
44
+ PAGE_UP: 33,
45
+ PERIOD: 190,
46
+ RIGHT: 39,
47
+ SPACE: 32,
48
+ TAB: 9,
49
+ UP: 38
50
+ }
51
+ });
52
+
53
+ // plugins
54
+ $.fn.extend({
55
+ _focus: $.fn.focus,
56
+ focus: function( delay, fn ) {
57
+ return typeof delay === "number" ?
58
+ this.each(function() {
59
+ var elem = this;
60
+ setTimeout(function() {
61
+ $( elem ).focus();
62
+ if ( fn ) {
63
+ fn.call( elem );
64
+ }
65
+ }, delay );
66
+ }) :
67
+ this._focus.apply( this, arguments );
68
+ },
69
+
70
+ scrollParent: function() {
71
+ var scrollParent;
72
+ if (($.ui.ie && (/(static|relative)/).test(this.css('position'))) || (/absolute/).test(this.css('position'))) {
73
+ scrollParent = this.parents().filter(function() {
74
+ return (/(relative|absolute|fixed)/).test($.css(this,'position')) && (/(auto|scroll)/).test($.css(this,'overflow')+$.css(this,'overflow-y')+$.css(this,'overflow-x'));
75
+ }).eq(0);
76
+ } else {
77
+ scrollParent = this.parents().filter(function() {
78
+ return (/(auto|scroll)/).test($.css(this,'overflow')+$.css(this,'overflow-y')+$.css(this,'overflow-x'));
79
+ }).eq(0);
80
+ }
81
+
82
+ return (/fixed/).test(this.css('position')) || !scrollParent.length ? $(document) : scrollParent;
83
+ },
84
+
85
+ zIndex: function( zIndex ) {
86
+ if ( zIndex !== undefined ) {
87
+ return this.css( "zIndex", zIndex );
88
+ }
89
+
90
+ if ( this.length ) {
91
+ var elem = $( this[ 0 ] ), position, value;
92
+ while ( elem.length && elem[ 0 ] !== document ) {
93
+ // Ignore z-index if position is set to a value where z-index is ignored by the browser
94
+ // This makes behavior of this function consistent across browsers
95
+ // WebKit always returns auto if the element is positioned
96
+ position = elem.css( "position" );
97
+ if ( position === "absolute" || position === "relative" || position === "fixed" ) {
98
+ // IE returns 0 when zIndex is not specified
99
+ // other browsers return a string
100
+ // we ignore the case of nested elements with an explicit value of 0
101
+ // <div style="z-index: -10;"><div style="z-index: 0;"></div></div>
102
+ value = parseInt( elem.css( "zIndex" ), 10 );
103
+ if ( !isNaN( value ) && value !== 0 ) {
104
+ return value;
105
+ }
106
+ }
107
+ elem = elem.parent();
108
+ }
109
+ }
110
+
111
+ return 0;
112
+ },
113
+
114
+ uniqueId: function() {
115
+ return this.each(function() {
116
+ if ( !this.id ) {
117
+ this.id = "ui-id-" + (++uuid);
118
+ }
119
+ });
120
+ },
121
+
122
+ removeUniqueId: function() {
123
+ return this.each(function() {
124
+ if ( runiqueId.test( this.id ) ) {
125
+ $( this ).removeAttr( "id" );
126
+ }
127
+ });
128
+ }
129
+ });
130
+
131
+ // selectors
132
+ function focusable( element, isTabIndexNotNaN ) {
133
+ var map, mapName, img,
134
+ nodeName = element.nodeName.toLowerCase();
135
+ if ( "area" === nodeName ) {
136
+ map = element.parentNode;
137
+ mapName = map.name;
138
+ if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) {
139
+ return false;
140
+ }
141
+ img = $( "img[usemap=#" + mapName + "]" )[0];
142
+ return !!img && visible( img );
143
+ }
144
+ return ( /input|select|textarea|button|object/.test( nodeName ) ?
145
+ !element.disabled :
146
+ "a" === nodeName ?
147
+ element.href || isTabIndexNotNaN :
148
+ isTabIndexNotNaN) &&
149
+ // the element and all of its ancestors must be visible
150
+ visible( element );
151
+ }
152
+
153
+ function visible( element ) {
154
+ return $.expr.filters.visible( element ) &&
155
+ !$( element ).parents().andSelf().filter(function() {
156
+ return $.css( this, "visibility" ) === "hidden";
157
+ }).length;
158
+ }
159
+
160
+ $.extend( $.expr[ ":" ], {
161
+ data: $.expr.createPseudo ?
162
+ $.expr.createPseudo(function( dataName ) {
163
+ return function( elem ) {
164
+ return !!$.data( elem, dataName );
165
+ };
166
+ }) :
167
+ // support: jQuery <1.8
168
+ function( elem, i, match ) {
169
+ return !!$.data( elem, match[ 3 ] );
170
+ },
171
+
172
+ focusable: function( element ) {
173
+ return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) );
174
+ },
175
+
176
+ tabbable: function( element ) {
177
+ var tabIndex = $.attr( element, "tabindex" ),
178
+ isTabIndexNaN = isNaN( tabIndex );
179
+ return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN );
180
+ }
181
+ });
182
+
183
+ // support
184
+ $(function() {
185
+ var body = document.body,
186
+ div = body.appendChild( div = document.createElement( "div" ) );
187
+
188
+ // access offsetHeight before setting the style to prevent a layout bug
189
+ // in IE 9 which causes the element to continue to take up space even
190
+ // after it is removed from the DOM (#8026)
191
+ div.offsetHeight;
192
+
193
+ $.extend( div.style, {
194
+ minHeight: "100px",
195
+ height: "auto",
196
+ padding: 0,
197
+ borderWidth: 0
198
+ });
199
+
200
+ $.support.minHeight = div.offsetHeight === 100;
201
+ $.support.selectstart = "onselectstart" in div;
202
+
203
+ // set display to none to avoid a layout bug in IE
204
+ // http://dev.jquery.com/ticket/4014
205
+ body.removeChild( div ).style.display = "none";
206
+ });
207
+
208
+ // support: jQuery <1.8
209
+ if ( !$( "<a>" ).outerWidth( 1 ).jquery ) {
210
+ $.each( [ "Width", "Height" ], function( i, name ) {
211
+ var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ],
212
+ type = name.toLowerCase(),
213
+ orig = {
214
+ innerWidth: $.fn.innerWidth,
215
+ innerHeight: $.fn.innerHeight,
216
+ outerWidth: $.fn.outerWidth,
217
+ outerHeight: $.fn.outerHeight
218
+ };
219
+
220
+ function reduce( elem, size, border, margin ) {
221
+ $.each( side, function() {
222
+ size -= parseFloat( $.css( elem, "padding" + this ) ) || 0;
223
+ if ( border ) {
224
+ size -= parseFloat( $.css( elem, "border" + this + "Width" ) ) || 0;
225
+ }
226
+ if ( margin ) {
227
+ size -= parseFloat( $.css( elem, "margin" + this ) ) || 0;
228
+ }
229
+ });
230
+ return size;
231
+ }
232
+
233
+ $.fn[ "inner" + name ] = function( size ) {
234
+ if ( size === undefined ) {
235
+ return orig[ "inner" + name ].call( this );
236
+ }
237
+
238
+ return this.each(function() {
239
+ $( this ).css( type, reduce( this, size ) + "px" );
240
+ });
241
+ };
242
+
243
+ $.fn[ "outer" + name] = function( size, margin ) {
244
+ if ( typeof size !== "number" ) {
245
+ return orig[ "outer" + name ].call( this, size );
246
+ }
247
+
248
+ return this.each(function() {
249
+ $( this).css( type, reduce( this, size, true, margin ) + "px" );
250
+ });
251
+ };
252
+ });
253
+ }
254
+
255
+ // support: jQuery 1.6.1, 1.6.2 (http://bugs.jquery.com/ticket/9413)
256
+ if ( $( "<a>" ).data( "a-b", "a" ).removeData( "a-b" ).data( "a-b" ) ) {
257
+ $.fn.removeData = (function( removeData ) {
258
+ return function( key ) {
259
+ if ( arguments.length ) {
260
+ return removeData.call( this, $.camelCase( key ) );
261
+ } else {
262
+ return removeData.call( this );
263
+ }
264
+ };
265
+ })( $.fn.removeData );
266
+ }
267
+
268
+
269
+
270
+
271
+
272
+ // deprecated
273
+
274
+ (function() {
275
+ var uaMatch = /msie ([\w.]+)/.exec( navigator.userAgent.toLowerCase() ) || [];
276
+ $.ui.ie = uaMatch.length ? true : false;
277
+ $.ui.ie6 = parseFloat( uaMatch[ 1 ], 10 ) === 6;
278
+ })();
279
+
280
+ $.fn.extend({
281
+ disableSelection: function() {
282
+ return this.bind( ( $.support.selectstart ? "selectstart" : "mousedown" ) +
283
+ ".ui-disableSelection", function( event ) {
284
+ event.preventDefault();
285
+ });
286
+ },
287
+
288
+ enableSelection: function() {
289
+ return this.unbind( ".ui-disableSelection" );
290
+ }
291
+ });
292
+
293
+ $.extend( $.ui, {
294
+ // $.ui.plugin is deprecated. Use the proxy pattern instead.
295
+ plugin: {
296
+ add: function( module, option, set ) {
297
+ var i,
298
+ proto = $.ui[ module ].prototype;
299
+ for ( i in set ) {
300
+ proto.plugins[ i ] = proto.plugins[ i ] || [];
301
+ proto.plugins[ i ].push( [ option, set[ i ] ] );
302
+ }
303
+ },
304
+ call: function( instance, name, args ) {
305
+ var i,
306
+ set = instance.plugins[ name ];
307
+ if ( !set || !instance.element[ 0 ].parentNode || instance.element[ 0 ].parentNode.nodeType === 11 ) {
308
+ return;
309
+ }
310
+
311
+ for ( i = 0; i < set.length; i++ ) {
312
+ if ( instance.options[ set[ i ][ 0 ] ] ) {
313
+ set[ i ][ 1 ].apply( instance.element, args );
314
+ }
315
+ }
316
+ }
317
+ },
318
+
319
+ contains: $.contains,
320
+
321
+ // only used by resizable
322
+ hasScroll: function( el, a ) {
323
+
324
+ //If overflow is hidden, the element might have extra content, but the user wants to hide it
325
+ if ( $( el ).css( "overflow" ) === "hidden") {
326
+ return false;
327
+ }
328
+
329
+ var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop",
330
+ has = false;
331
+
332
+ if ( el[ scroll ] > 0 ) {
333
+ return true;
334
+ }
335
+
336
+ // TODO: determine which cases actually cause this to happen
337
+ // if the element doesn't have the scroll set, see if it's possible to
338
+ // set the scroll
339
+ el[ scroll ] = 1;
340
+ has = ( el[ scroll ] > 0 );
341
+ el[ scroll ] = 0;
342
+ return has;
343
+ },
344
+
345
+ // these are odd functions, fix the API or move into individual plugins
346
+ isOverAxis: function( x, reference, size ) {
347
+ //Determines when x coordinate is over "b" element axis
348
+ return ( x > reference ) && ( x < ( reference + size ) );
349
+ },
350
+ isOver: function( y, x, top, left, height, width ) {
351
+ //Determines when x, y coordinates is over "b" element
352
+ return $.ui.isOverAxis( y, top, height ) && $.ui.isOverAxis( x, left, width );
353
+ }
354
+ });
355
+
356
+ })( jQuery );
@@ -0,0 +1,610 @@
1
+ /*!
2
+ * jQuery UI Menu @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/menu/
10
+ *
11
+ * Depends:
12
+ * jquery.ui.core.js
13
+ * jquery.ui.widget.js
14
+ * jquery.ui.position.js
15
+ */
16
+ (function( $, undefined ) {
17
+
18
+ var mouseHandled = false;
19
+
20
+ $.widget( "ui.menu", {
21
+ version: "@VERSION",
22
+ defaultElement: "<ul>",
23
+ delay: 300,
24
+ options: {
25
+ icons: {
26
+ submenu: "ui-icon-carat-1-e"
27
+ },
28
+ menus: "ul",
29
+ position: {
30
+ my: "left top",
31
+ at: "right top"
32
+ },
33
+ role: "menu",
34
+
35
+ // callbacks
36
+ blur: null,
37
+ focus: null,
38
+ select: null
39
+ },
40
+
41
+ _create: function() {
42
+ this.activeMenu = this.element;
43
+ this.element
44
+ .uniqueId()
45
+ .addClass( "ui-menu ui-widget ui-widget-content ui-corner-all" )
46
+ .toggleClass( "ui-menu-icons", !!this.element.find( ".ui-icon" ).length )
47
+ .attr({
48
+ role: this.options.role,
49
+ tabIndex: 0
50
+ })
51
+ // need to catch all clicks on disabled menu
52
+ // not possible through _on
53
+ .bind( "click" + this.eventNamespace, $.proxy(function( event ) {
54
+ if ( this.options.disabled ) {
55
+ event.preventDefault();
56
+ }
57
+ }, this ));
58
+
59
+ if ( this.options.disabled ) {
60
+ this.element
61
+ .addClass( "ui-state-disabled" )
62
+ .attr( "aria-disabled", "true" );
63
+ }
64
+
65
+ this._on({
66
+ // Prevent focus from sticking to links inside menu after clicking
67
+ // them (focus should always stay on UL during navigation).
68
+ "mousedown .ui-menu-item > a": function( event ) {
69
+ event.preventDefault();
70
+ },
71
+ "click .ui-state-disabled > a": function( event ) {
72
+ event.preventDefault();
73
+ },
74
+ "click .ui-menu-item:has(a)": function( event ) {
75
+ var target = $( event.target ).closest( ".ui-menu-item" );
76
+ if ( !mouseHandled && target.not( ".ui-state-disabled" ).length ) {
77
+ mouseHandled = true;
78
+
79
+ this.select( event );
80
+ // Open submenu on click
81
+ if ( target.has( ".ui-menu" ).length ) {
82
+ this.expand( event );
83
+ } else if ( !this.element.is( ":focus" ) ) {
84
+ // Redirect focus to the menu
85
+ this.element.trigger( "focus", [ true ] );
86
+
87
+ // If the active item is on the top level, let it stay active.
88
+ // Otherwise, blur the active item since it is no longer visible.
89
+ if ( this.active && this.active.parents( ".ui-menu" ).length === 1 ) {
90
+ clearTimeout( this.timer );
91
+ }
92
+ }
93
+ }
94
+ },
95
+ "mouseenter .ui-menu-item": function( event ) {
96
+ var target = $( event.currentTarget );
97
+ // Remove ui-state-active class from siblings of the newly focused menu item
98
+ // to avoid a jump caused by adjacent elements both having a class with a border
99
+ target.siblings().children( ".ui-state-active" ).removeClass( "ui-state-active" );
100
+ this.focus( event, target );
101
+ },
102
+ mouseleave: "collapseAll",
103
+ "mouseleave .ui-menu": "collapseAll",
104
+ focus: function( event, keepActiveItem ) {
105
+ // If there's already an active item, keep it active
106
+ // If not, activate the first item
107
+ var item = this.active || this.element.children( ".ui-menu-item" ).eq( 0 );
108
+
109
+ if ( !keepActiveItem ) {
110
+ this.focus( event, item );
111
+ }
112
+ },
113
+ blur: function( event ) {
114
+ this._delay(function() {
115
+ if ( !$.contains( this.element[0], this.document[0].activeElement ) ) {
116
+ this.collapseAll( event );
117
+ }
118
+ });
119
+ },
120
+ keydown: "_keydown"
121
+ });
122
+
123
+ this.refresh();
124
+
125
+ // Clicks outside of a menu collapse any open menus
126
+ this._on( this.document, {
127
+ click: function( event ) {
128
+ if ( !$( event.target ).closest( ".ui-menu" ).length ) {
129
+ this.collapseAll( event );
130
+ }
131
+
132
+ // Reset the mouseHandled flag
133
+ mouseHandled = false;
134
+ }
135
+ });
136
+ },
137
+
138
+ _destroy: function() {
139
+ // Destroy (sub)menus
140
+ this.element
141
+ .removeAttr( "aria-activedescendant" )
142
+ .find( ".ui-menu" ).andSelf()
143
+ .removeClass( "ui-menu ui-widget ui-widget-content ui-corner-all ui-menu-icons" )
144
+ .removeAttr( "role" )
145
+ .removeAttr( "tabIndex" )
146
+ .removeAttr( "aria-labelledby" )
147
+ .removeAttr( "aria-expanded" )
148
+ .removeAttr( "aria-hidden" )
149
+ .removeAttr( "aria-disabled" )
150
+ .removeUniqueId()
151
+ .show();
152
+
153
+ // Destroy menu items
154
+ this.element.find( ".ui-menu-item" )
155
+ .removeClass( "ui-menu-item" )
156
+ .removeAttr( "role" )
157
+ .removeAttr( "aria-disabled" )
158
+ .children( "a" )
159
+ .removeUniqueId()
160
+ .removeClass( "ui-corner-all ui-state-hover" )
161
+ .removeAttr( "tabIndex" )
162
+ .removeAttr( "role" )
163
+ .removeAttr( "aria-haspopup" )
164
+ .children().each( function() {
165
+ var elem = $( this );
166
+ if ( elem.data( "ui-menu-submenu-carat" ) ) {
167
+ elem.remove();
168
+ }
169
+ });
170
+
171
+ // Destroy menu dividers
172
+ this.element.find( ".ui-menu-divider" ).removeClass( "ui-menu-divider ui-widget-content" );
173
+ },
174
+
175
+ _keydown: function( event ) {
176
+ var match, prev, character, skip, regex,
177
+ preventDefault = true;
178
+
179
+ function escape( value ) {
180
+ return value.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" );
181
+ }
182
+
183
+ switch ( event.keyCode ) {
184
+ case $.ui.keyCode.PAGE_UP:
185
+ this.previousPage( event );
186
+ break;
187
+ case $.ui.keyCode.PAGE_DOWN:
188
+ this.nextPage( event );
189
+ break;
190
+ case $.ui.keyCode.HOME:
191
+ this._move( "first", "first", event );
192
+ break;
193
+ case $.ui.keyCode.END:
194
+ this._move( "last", "last", event );
195
+ break;
196
+ case $.ui.keyCode.UP:
197
+ this.previous( event );
198
+ break;
199
+ case $.ui.keyCode.DOWN:
200
+ this.next( event );
201
+ break;
202
+ case $.ui.keyCode.LEFT:
203
+ this.collapse( event );
204
+ break;
205
+ case $.ui.keyCode.RIGHT:
206
+ if ( this.active && !this.active.is( ".ui-state-disabled" ) ) {
207
+ this.expand( event );
208
+ }
209
+ break;
210
+ case $.ui.keyCode.ENTER:
211
+ case $.ui.keyCode.SPACE:
212
+ this._activate( event );
213
+ break;
214
+ case $.ui.keyCode.ESCAPE:
215
+ this.collapse( event );
216
+ break;
217
+ default:
218
+ preventDefault = false;
219
+ prev = this.previousFilter || "";
220
+ character = String.fromCharCode( event.keyCode );
221
+ skip = false;
222
+
223
+ clearTimeout( this.filterTimer );
224
+
225
+ if ( character === prev ) {
226
+ skip = true;
227
+ } else {
228
+ character = prev + character;
229
+ }
230
+
231
+ regex = new RegExp( "^" + escape( character ), "i" );
232
+ match = this.activeMenu.children( ".ui-menu-item" ).filter(function() {
233
+ return regex.test( $( this ).children( "a" ).text() );
234
+ });
235
+ match = skip && match.index( this.active.next() ) !== -1 ?
236
+ this.active.nextAll( ".ui-menu-item" ) :
237
+ match;
238
+
239
+ // If no matches on the current filter, reset to the last character pressed
240
+ // to move down the menu to the first item that starts with that character
241
+ if ( !match.length ) {
242
+ character = String.fromCharCode( event.keyCode );
243
+ regex = new RegExp( "^" + escape( character ), "i" );
244
+ match = this.activeMenu.children( ".ui-menu-item" ).filter(function() {
245
+ return regex.test( $( this ).children( "a" ).text() );
246
+ });
247
+ }
248
+
249
+ if ( match.length ) {
250
+ this.focus( event, match );
251
+ if ( match.length > 1 ) {
252
+ this.previousFilter = character;
253
+ this.filterTimer = this._delay(function() {
254
+ delete this.previousFilter;
255
+ }, 1000 );
256
+ } else {
257
+ delete this.previousFilter;
258
+ }
259
+ } else {
260
+ delete this.previousFilter;
261
+ }
262
+ }
263
+
264
+ if ( preventDefault ) {
265
+ event.preventDefault();
266
+ }
267
+ },
268
+
269
+ _activate: function( event ) {
270
+ if ( !this.active.is( ".ui-state-disabled" ) ) {
271
+ if ( this.active.children( "a[aria-haspopup='true']" ).length ) {
272
+ this.expand( event );
273
+ } else {
274
+ this.select( event );
275
+ }
276
+ }
277
+ },
278
+
279
+ refresh: function() {
280
+ var menus,
281
+ icon = this.options.icons.submenu,
282
+ submenus = this.element.find( this.options.menus );
283
+
284
+ // Initialize nested menus
285
+ submenus.filter( ":not(.ui-menu)" )
286
+ .addClass( "ui-menu ui-widget ui-widget-content ui-corner-all" )
287
+ .hide()
288
+ .attr({
289
+ role: this.options.role,
290
+ "aria-hidden": "true",
291
+ "aria-expanded": "false"
292
+ })
293
+ .each(function() {
294
+ var menu = $( this ),
295
+ item = menu.prev( "a" ),
296
+ submenuCarat = $( "<span>" )
297
+ .addClass( "ui-menu-icon ui-icon " + icon )
298
+ .data( "ui-menu-submenu-carat", true );
299
+
300
+ item
301
+ .attr( "aria-haspopup", "true" )
302
+ .prepend( submenuCarat );
303
+ menu.attr( "aria-labelledby", item.attr( "id" ) );
304
+ });
305
+
306
+ menus = submenus.add( this.element );
307
+
308
+ // Don't refresh list items that are already adapted
309
+ menus.children( ":not(.ui-menu-item):has(a)" )
310
+ .addClass( "ui-menu-item" )
311
+ .attr( "role", "presentation" )
312
+ .children( "a" )
313
+ .uniqueId()
314
+ .addClass( "ui-corner-all" )
315
+ .attr({
316
+ tabIndex: -1,
317
+ role: this._itemRole()
318
+ });
319
+
320
+ // Initialize unlinked menu-items containing spaces and/or dashes only as dividers
321
+ menus.children( ":not(.ui-menu-item)" ).each(function() {
322
+ var item = $( this );
323
+ // hyphen, em dash, en dash
324
+ if ( !/[^\-—–\s]/.test( item.text() ) ) {
325
+ item.addClass( "ui-widget-content ui-menu-divider" );
326
+ }
327
+ });
328
+
329
+ // Add aria-disabled attribute to any disabled menu item
330
+ menus.children( ".ui-state-disabled" ).attr( "aria-disabled", "true" );
331
+
332
+ // If the active item has been removed, blur the menu
333
+ if ( this.active && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) {
334
+ this.blur();
335
+ }
336
+ },
337
+
338
+ _itemRole: function() {
339
+ return {
340
+ menu: "menuitem",
341
+ listbox: "option"
342
+ }[ this.options.role ];
343
+ },
344
+
345
+ focus: function( event, item ) {
346
+ var nested, focused;
347
+ this.blur( event, event && event.type === "focus" );
348
+
349
+ this._scrollIntoView( item );
350
+
351
+ this.active = item.first();
352
+ focused = this.active.children( "a" ).addClass( "ui-state-focus" );
353
+ // Only update aria-activedescendant if there's a role
354
+ // otherwise we assume focus is managed elsewhere
355
+ if ( this.options.role ) {
356
+ this.element.attr( "aria-activedescendant", focused.attr( "id" ) );
357
+ }
358
+
359
+ // Highlight active parent menu item, if any
360
+ this.active
361
+ .parent()
362
+ .closest( ".ui-menu-item" )
363
+ .children( "a:first" )
364
+ .addClass( "ui-state-active" );
365
+
366
+ if ( event && event.type === "keydown" ) {
367
+ this._close();
368
+ } else {
369
+ this.timer = this._delay(function() {
370
+ this._close();
371
+ }, this.delay );
372
+ }
373
+
374
+ nested = item.children( ".ui-menu" );
375
+ if ( nested.length && ( /^mouse/.test( event.type ) ) ) {
376
+ this._startOpening(nested);
377
+ }
378
+ this.activeMenu = item.parent();
379
+
380
+ this._trigger( "focus", event, { item: item } );
381
+ },
382
+
383
+ _scrollIntoView: function( item ) {
384
+ var borderTop, paddingTop, offset, scroll, elementHeight, itemHeight;
385
+ if ( this._hasScroll() ) {
386
+ borderTop = parseFloat( $.css( this.activeMenu[0], "borderTopWidth" ) ) || 0;
387
+ paddingTop = parseFloat( $.css( this.activeMenu[0], "paddingTop" ) ) || 0;
388
+ offset = item.offset().top - this.activeMenu.offset().top - borderTop - paddingTop;
389
+ scroll = this.activeMenu.scrollTop();
390
+ elementHeight = this.activeMenu.height();
391
+ itemHeight = item.height();
392
+
393
+ if ( offset < 0 ) {
394
+ this.activeMenu.scrollTop( scroll + offset );
395
+ } else if ( offset + itemHeight > elementHeight ) {
396
+ this.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight );
397
+ }
398
+ }
399
+ },
400
+
401
+ blur: function( event, fromFocus ) {
402
+ if ( !fromFocus ) {
403
+ clearTimeout( this.timer );
404
+ }
405
+
406
+ if ( !this.active ) {
407
+ return;
408
+ }
409
+
410
+ this.active.children( "a" ).removeClass( "ui-state-focus" );
411
+ this.active = null;
412
+
413
+ this._trigger( "blur", event, { item: this.active } );
414
+ },
415
+
416
+ _startOpening: function( submenu ) {
417
+ clearTimeout( this.timer );
418
+
419
+ // Don't open if already open fixes a Firefox bug that caused a .5 pixel
420
+ // shift in the submenu position when mousing over the carat icon
421
+ if ( submenu.attr( "aria-hidden" ) !== "true" ) {
422
+ return;
423
+ }
424
+
425
+ this.timer = this._delay(function() {
426
+ this._close();
427
+ this._open( submenu );
428
+ }, this.delay );
429
+ },
430
+
431
+ _open: function( submenu ) {
432
+ var position = $.extend({
433
+ of: this.active
434
+ }, this.options.position );
435
+
436
+ clearTimeout( this.timer );
437
+ this.element.find( ".ui-menu" ).not( submenu.parents( ".ui-menu" ) )
438
+ .hide()
439
+ .attr( "aria-hidden", "true" );
440
+
441
+ submenu
442
+ .show()
443
+ .removeAttr( "aria-hidden" )
444
+ .attr( "aria-expanded", "true" )
445
+ .position( position );
446
+ },
447
+
448
+ collapseAll: function( event, all ) {
449
+ clearTimeout( this.timer );
450
+ this.timer = this._delay(function() {
451
+ // If we were passed an event, look for the submenu that contains the event
452
+ var currentMenu = all ? this.element :
453
+ $( event && event.target ).closest( this.element.find( ".ui-menu" ) );
454
+
455
+ // If we found no valid submenu ancestor, use the main menu to close all sub menus anyway
456
+ if ( !currentMenu.length ) {
457
+ currentMenu = this.element;
458
+ }
459
+
460
+ this._close( currentMenu );
461
+
462
+ this.blur( event );
463
+ this.activeMenu = currentMenu;
464
+ }, this.delay );
465
+ },
466
+
467
+ // With no arguments, closes the currently active menu - if nothing is active
468
+ // it closes all menus. If passed an argument, it will search for menus BELOW
469
+ _close: function( startMenu ) {
470
+ if ( !startMenu ) {
471
+ startMenu = this.active ? this.active.parent() : this.element;
472
+ }
473
+
474
+ startMenu
475
+ .find( ".ui-menu" )
476
+ .hide()
477
+ .attr( "aria-hidden", "true" )
478
+ .attr( "aria-expanded", "false" )
479
+ .end()
480
+ .find( "a.ui-state-active" )
481
+ .removeClass( "ui-state-active" );
482
+ },
483
+
484
+ collapse: function( event ) {
485
+ var newItem = this.active &&
486
+ this.active.parent().closest( ".ui-menu-item", this.element );
487
+ if ( newItem && newItem.length ) {
488
+ this._close();
489
+ this.focus( event, newItem );
490
+ }
491
+ },
492
+
493
+ expand: function( event ) {
494
+ var newItem = this.active &&
495
+ this.active
496
+ .children( ".ui-menu " )
497
+ .children( ".ui-menu-item" )
498
+ .first();
499
+
500
+ if ( newItem && newItem.length ) {
501
+ this._open( newItem.parent() );
502
+
503
+ // Delay so Firefox will not hide activedescendant change in expanding submenu from AT
504
+ this._delay(function() {
505
+ this.focus( event, newItem );
506
+ });
507
+ }
508
+ },
509
+
510
+ next: function( event ) {
511
+ this._move( "next", "first", event );
512
+ },
513
+
514
+ previous: function( event ) {
515
+ this._move( "prev", "last", event );
516
+ },
517
+
518
+ isFirstItem: function() {
519
+ return this.active && !this.active.prevAll( ".ui-menu-item" ).length;
520
+ },
521
+
522
+ isLastItem: function() {
523
+ return this.active && !this.active.nextAll( ".ui-menu-item" ).length;
524
+ },
525
+
526
+ _move: function( direction, filter, event ) {
527
+ var next;
528
+ if ( this.active ) {
529
+ if ( direction === "first" || direction === "last" ) {
530
+ next = this.active
531
+ [ direction === "first" ? "prevAll" : "nextAll" ]( ".ui-menu-item" )
532
+ .eq( -1 );
533
+ } else {
534
+ next = this.active
535
+ [ direction + "All" ]( ".ui-menu-item" )
536
+ .eq( 0 );
537
+ }
538
+ }
539
+ if ( !next || !next.length || !this.active ) {
540
+ next = this.activeMenu.children( ".ui-menu-item" )[ filter ]();
541
+ }
542
+
543
+ this.focus( event, next );
544
+ },
545
+
546
+ nextPage: function( event ) {
547
+ var item, base, height;
548
+
549
+ if ( !this.active ) {
550
+ this.next( event );
551
+ return;
552
+ }
553
+ if ( this.isLastItem() ) {
554
+ return;
555
+ }
556
+ if ( this._hasScroll() ) {
557
+ base = this.active.offset().top;
558
+ height = this.element.height();
559
+ this.active.nextAll( ".ui-menu-item" ).each(function() {
560
+ item = $( this );
561
+ return item.offset().top - base - height < 0;
562
+ });
563
+
564
+ this.focus( event, item );
565
+ } else {
566
+ this.focus( event, this.activeMenu.children( ".ui-menu-item" )
567
+ [ !this.active ? "first" : "last" ]() );
568
+ }
569
+ },
570
+
571
+ previousPage: function( event ) {
572
+ var item, base, height;
573
+ if ( !this.active ) {
574
+ this.next( event );
575
+ return;
576
+ }
577
+ if ( this.isFirstItem() ) {
578
+ return;
579
+ }
580
+ if ( this._hasScroll() ) {
581
+ base = this.active.offset().top;
582
+ height = this.element.height();
583
+ this.active.prevAll( ".ui-menu-item" ).each(function() {
584
+ item = $( this );
585
+ return item.offset().top - base + height > 0;
586
+ });
587
+
588
+ this.focus( event, item );
589
+ } else {
590
+ this.focus( event, this.activeMenu.children( ".ui-menu-item" ).first() );
591
+ }
592
+ },
593
+
594
+ _hasScroll: function() {
595
+ return this.element.outerHeight() < this.element.prop( "scrollHeight" );
596
+ },
597
+
598
+ select: function( event ) {
599
+ // TODO: It should never be possible to not have an active item at this
600
+ // point, but the tests don't trigger mouseenter before click.
601
+ this.active = this.active || $( event.target ).closest( ".ui-menu-item" );
602
+ var ui = { item: this.active };
603
+ if ( !this.active.has( ".ui-menu" ).length ) {
604
+ this.collapseAll( event, true );
605
+ }
606
+ this._trigger( "select", event, ui );
607
+ }
608
+ });
609
+
610
+ }( jQuery ));