diff options
Diffstat (limited to 'resources/lib/oojs-ui/oojs-ui.js')
-rw-r--r-- | resources/lib/oojs-ui/oojs-ui.js | 227 |
1 files changed, 161 insertions, 66 deletions
diff --git a/resources/lib/oojs-ui/oojs-ui.js b/resources/lib/oojs-ui/oojs-ui.js index ff1163117919..d965ffa7ade8 100644 --- a/resources/lib/oojs-ui/oojs-ui.js +++ b/resources/lib/oojs-ui/oojs-ui.js @@ -1,12 +1,12 @@ /*! - * OOjs UI v0.1.0-pre (0a7180f468) + * OOjs UI v0.1.0-pre (70f1886a35) * https://www.mediawiki.org/wiki/OOjs_UI * * Copyright 2011–2014 OOjs Team and other contributors. * Released under the MIT license * http://oojs.mit-license.org * - * Date: Wed Apr 23 2014 18:05:30 GMT-0700 (PDT) + * Date: Tue Apr 29 2014 17:13:10 GMT-0700 (PDT) */ ( function ( OO ) { @@ -173,7 +173,8 @@ OO.ui.resolveMsg = function ( msg ) { * @param {Object} [config] Configuration options * @cfg {Function} [$] jQuery for the frame the widget is in * @cfg {string[]} [classes] CSS class names - * @cfg {jQuery} [$content] Content elements to append + * @cfg {string} [text] Text to insert + * @cfg {jQuery} [$content] Content elements to append (after text) */ OO.ui.Element = function OoUiElement( config ) { // Configuration initialization @@ -188,6 +189,9 @@ OO.ui.Element = function OoUiElement( config ) { if ( $.isArray( config.classes ) ) { this.$element.addClass( config.classes.join( ' ' ) ); } + if ( config.text ) { + this.$element.text( config.text ); + } if ( config.$content ) { this.$element.append( config.$content ); } @@ -612,32 +616,52 @@ OO.ui.Element.prototype.offDOMEvent = function ( event, callback ) { ( function () { // Static - var specialFocusin; - function handler( e ) { - jQuery.event.simulate( 'focusin', e.target, jQuery.event.fix( e ), /* bubble = */ true ); - } + // jQuery 1.8.3 has a bug with handling focusin/focusout events inside iframes. + // Firefox doesn't support focusin/focusout at all, so we listen for 'focus'/'blur' on the + // document, and simulate a 'focusin'/'focusout' event on the target element and make + // it bubble from there. + // + // - http://jsfiddle.net/sw3hr/ + // - http://bugs.jquery.com/ticket/14180 + // - https://github.com/jquery/jquery/commit/1cecf64e5aa4153 + function specialEvent( simulatedName, realName ) { + function handler( e ) { + jQuery.event.simulate( + simulatedName, + e.target, + jQuery.event.fix( e ), + /* bubble = */ true + ); + } - specialFocusin = { - setup: function () { - var doc = this.ownerDocument || this, - attaches = $.data( doc, 'ooui-focusin-attaches' ); - if ( !attaches ) { - doc.addEventListener( 'focus', handler, true ); - } - $.data( doc, 'ooui-focusin-attaches', ( attaches || 0 ) + 1 ); - }, - teardown: function () { - var doc = this.ownerDocument || this, - attaches = $.data( doc, 'ooui-focusin-attaches' ) - 1; - if ( !attaches ) { - doc.removeEventListener( 'focus', handler, true ); - $.removeData( doc, 'ooui-focusin-attaches' ); - } else { - $.data( doc, 'ooui-focusin-attaches', attaches ); + return { + setup: function () { + var doc = this.ownerDocument || this, + attaches = $.data( doc, 'ooui-' + simulatedName + '-attaches' ); + if ( !attaches ) { + doc.addEventListener( realName, handler, true ); + } + $.data( doc, 'ooui-' + simulatedName + '-attaches', ( attaches || 0 ) + 1 ); + }, + teardown: function () { + var doc = this.ownerDocument || this, + attaches = $.data( doc, 'ooui-' + simulatedName + '-attaches' ) - 1; + if ( !attaches ) { + doc.removeEventListener( realName, handler, true ); + $.removeData( doc, 'ooui-' + simulatedName + '-attaches' ); + } else { + $.data( doc, 'ooui-' + simulatedName + '-attaches', attaches ); + } } - } - }; + }; + } + + var hasOwn = Object.prototype.hasOwnProperty, + specialEvents = { + focusin: specialEvent( 'focusin', 'focus' ), + focusout: specialEvent( 'focusout', 'blur' ) + }; /** * Bind a handler for an event on a DOM element. @@ -654,25 +678,15 @@ OO.ui.Element.prototype.offDOMEvent = function ( event, callback ) { OO.ui.Element.onDOMEvent = function ( el, event, callback ) { var orig; - if ( event === 'focusin' ) { - // jQuery 1.8.3 has a bug with handling focusin events inside iframes. - // Firefox doesn't support focusin at all, so we listen for 'focus' on the - // document, and simulate a 'focusin' event on the target element and make - // it bubble from there. - // - // - http://jsfiddle.net/sw3hr/ - // - http://bugs.jquery.com/ticket/14180 - // - https://github.com/jquery/jquery/commit/1cecf64e5aa4153 - + if ( hasOwn.call( specialEvents, event ) ) { // Replace jQuery's override with our own - orig = $.event.special.focusin; - $.event.special.focusin = specialFocusin; + orig = $.event.special[event]; + $.event.special[event] = specialEvents[event]; $( el ).on( event, callback ); // Restore - $.event.special.focusin = orig; - + $.event.special[event] = orig; } else { $( el ).on( event, callback ); } @@ -688,11 +702,15 @@ OO.ui.Element.prototype.offDOMEvent = function ( event, callback ) { */ OO.ui.Element.offDOMEvent = function ( el, event, callback ) { var orig; - if ( event === 'focusin' ) { - orig = $.event.special.focusin; - $.event.special.focusin = specialFocusin; + if ( hasOwn.call( specialEvents, event ) ) { + // Replace jQuery's override with our own + orig = $.event.special[event]; + $.event.special[event] = specialEvents[event]; + $( el ).off( event, callback ); - $.event.special.focusin = orig; + + // Restore + $.event.special[event] = orig; } else { $( el ).off( event, callback ); } @@ -1279,7 +1297,9 @@ OO.ui.Window.prototype.teardown = function () { * Do not override this method. See #setup for a way to make changes each time the window opens. * * @param {Object} [data] Window opening data + * @fires opening * @fires open + * @fires ready * @chainable */ OO.ui.Window.prototype.open = function ( data ) { @@ -1290,13 +1310,16 @@ OO.ui.Window.prototype.open = function ( data ) { this.visible = true; this.emit( 'opening', data ); this.setup( data ); - // Focus the content div (which has a tabIndex) to inactivate - // (but not clear) selections in the parent frame. - // Must happen after setup runs (otherwise focusing it doesn't work) - // but before 'open' is emitted (so subclasses can give focus to something else) - this.frame.$content.focus(); this.emit( 'open', data ); - this.opening = false; + setTimeout( OO.ui.bind( function () { + // Focus the content div (which has a tabIndex) to inactivate + // (but not clear) selections in the parent frame. + // Must happen after 'open' is emitted (to ensure it is visible) + // but before 'ready' is emitted (so subclasses can give focus to something else) + this.frame.$content.focus(); + this.emit( 'ready', data ); + this.opening = false; + }, this ) ); }, this ) ); } @@ -1309,6 +1332,7 @@ OO.ui.Window.prototype.open = function ( data ) { * See #teardown for a way to do something each time the window closes. * * @param {Object} [data] Window closing data + * @fires closing * @fires close * @chainable */ @@ -2206,7 +2230,6 @@ OO.ui.FlaggableElement.prototype.setFlags = function ( flags ) { * @constructor * @param {jQuery} $group Container node, assigned to #$group * @param {Object} [config] Configuration options - * @cfg {Object.<string,string>} [aggregations] Events to aggregate, keyed by item event name */ OO.ui.GroupElement = function OoUiGroupElement( $group, config ) { // Configuration @@ -2216,8 +2239,7 @@ OO.ui.GroupElement = function OoUiGroupElement( $group, config ) { this.$group = $group; this.items = []; this.$items = this.$( [] ); - this.aggregate = !$.isEmptyObject( config.aggregations ); - this.aggregations = config.aggregations || {}; + this.aggregateItemEvents = {}; }; /* Methods */ @@ -2232,6 +2254,59 @@ OO.ui.GroupElement.prototype.getItems = function () { }; /** + * Add an aggregate item event. + * + * Aggregated events are listened to on each item and then emitted by the group under a new name, + * and with an additional leading parameter containing the item that emitted the original event. + * Other arguments that were emitted from the original event are passed through. + * + * @param {Object.<string,string|null>} events Aggregate events emitted by group, keyed by item + * event, use null value to remove aggregation + * @throws {Error} If aggregation already exists + */ +OO.ui.GroupElement.prototype.aggregate = function ( events ) { + var i, len, item, add, remove, itemEvent, groupEvent; + + for ( itemEvent in events ) { + groupEvent = events[itemEvent]; + + // Remove existing aggregated event + if ( itemEvent in this.aggregateItemEvents ) { + // Don't allow duplicate aggregations + if ( groupEvent ) { + throw new Error( 'Duplicate item event aggregation for ' + itemEvent ); + } + // Remove event aggregation from existing items + for ( i = 0, len = this.items.length; i < len; i++ ) { + item = this.items[i]; + if ( item.connect && item.disconnect ) { + remove = {}; + remove[itemEvent] = [ 'emit', groupEvent, item ]; + item.disconnect( this, remove ); + } + } + // Prevent future items from aggregating event + delete this.aggregateItemEvents[itemEvent]; + } + + // Add new aggregate event + if ( groupEvent ) { + // Make future items aggregate event + this.aggregateItemEvents[itemEvent] = groupEvent; + // Add event aggregation to existing items + for ( i = 0, len = this.items.length; i < len; i++ ) { + item = this.items[i]; + if ( item.connect && item.disconnect ) { + add = {}; + add[itemEvent] = [ 'emit', groupEvent, item ]; + item.connect( this, add ); + } + } + } + } +}; + +/** * Add items. * * @param {OO.ui.Element[]} items Item @@ -2255,10 +2330,10 @@ OO.ui.GroupElement.prototype.addItems = function ( items, index ) { } } // Add the item - if ( this.aggregate ) { + if ( item.connect && item.disconnect && !$.isEmptyObject( this.aggregateItemEvents ) ) { events = {}; - for ( event in this.aggregations ) { - events[event] = [ 'emit', this.aggregations[event], item ]; + for ( event in this.aggregateItemEvents ) { + events[event] = [ 'emit', this.aggregateItemEvents[event], item ]; } item.connect( this, events ); } @@ -2291,15 +2366,22 @@ OO.ui.GroupElement.prototype.addItems = function ( items, index ) { * @chainable */ OO.ui.GroupElement.prototype.removeItems = function ( items ) { - var i, len, item, index; + var i, len, item, index, remove, itemEvent; // Remove specific items for ( i = 0, len = items.length; i < len; i++ ) { item = items[i]; index = $.inArray( item, this.items ); if ( index !== -1 ) { - if ( this.aggregate ) { - item.disconnect( this ); + if ( + item.connect && item.disconnect && + !$.isEmptyObject( this.aggregateItemEvents ) + ) { + remove = {}; + if ( itemEvent in this.aggregateItemEvents ) { + remove[itemEvent] = [ 'emit', this.aggregateItemEvents[itemEvent], item ]; + } + item.disconnect( this, remove ); } item.setElementGroup( null ); this.items.splice( index, 1 ); @@ -2319,13 +2401,20 @@ OO.ui.GroupElement.prototype.removeItems = function ( items ) { * @chainable */ OO.ui.GroupElement.prototype.clearItems = function () { - var i, len, item; + var i, len, item, remove, itemEvent; // Remove all items for ( i = 0, len = this.items.length; i < len; i++ ) { item = this.items[i]; - if ( this.aggregate ) { - item.disconnect( this ); + if ( + item.connect && item.disconnect && + !$.isEmptyObject( this.aggregateItemEvents ) + ) { + remove = {}; + if ( itemEvent in this.aggregateItemEvents ) { + remove[itemEvent] = [ 'emit', this.aggregateItemEvents[itemEvent], item ]; + } + item.disconnect( this, remove ); } item.setElementGroup( null ); } @@ -3344,9 +3433,7 @@ OO.ui.ToolFactory.prototype.extract = function ( collection, used ) { */ OO.ui.ToolGroup = function OoUiToolGroup( toolbar, config ) { // Configuration initialization - config = $.extend( true, { - 'aggregations': { 'disable': 'itemDisable' } - }, config ); + config = config || {}; // Parent constructor OO.ui.ToolGroup.super.call( this, config ); @@ -3373,6 +3460,7 @@ OO.ui.ToolGroup = function OoUiToolGroup( toolbar, config ) { 'mouseout': OO.ui.bind( this.onMouseOut, this ) } ); this.toolbar.getToolFactory().connect( this, { 'register': 'onToolFactoryRegister' } ); + this.aggregate( { 'disable': 'itemDisable' } ); this.connect( this, { 'itemDisable': 'updateDisabled' } ); // Initialization @@ -5464,7 +5552,7 @@ OO.ui.InputWidget.prototype.isReadOnly = function () { */ OO.ui.InputWidget.prototype.setReadOnly = function ( state ) { this.readOnly = !!state; - this.$input.prop( 'readonly', this.readOnly ); + this.$input.prop( 'readOnly', this.readOnly ); return this; }; @@ -7707,6 +7795,13 @@ OO.ui.SearchWidget.prototype.clear = function () { }; /** + * Focus the query input. + */ +OO.ui.SearchWidget.prototype.focus = function () { + this.query.$input[0].focus(); +}; + +/** * Get the results list. * * @return {OO.ui.SelectWidget} Select list |