diff options
author | Pierre Schmitz <pierre@archlinux.de> | 2015-12-17 09:15:42 +0100 |
---|---|---|
committer | Pierre Schmitz <pierre@archlinux.de> | 2015-12-17 09:44:51 +0100 |
commit | a1789ddde42033f1b05cc4929491214ee6e79383 (patch) | |
tree | 63615735c4ddffaaabf2428946bb26f90899f7bf /vendor/oojs/oojs-ui/src/Element.js | |
parent | 9e06a62f265e3a2aaabecc598d4bc617e06fa32d (diff) |
Update to MediaWiki 1.26.0
Diffstat (limited to 'vendor/oojs/oojs-ui/src/Element.js')
-rw-r--r-- | vendor/oojs/oojs-ui/src/Element.js | 748 |
1 files changed, 0 insertions, 748 deletions
diff --git a/vendor/oojs/oojs-ui/src/Element.js b/vendor/oojs/oojs-ui/src/Element.js deleted file mode 100644 index 127eb503..00000000 --- a/vendor/oojs/oojs-ui/src/Element.js +++ /dev/null @@ -1,748 +0,0 @@ -/** - * Each Element represents a rendering in the DOM—a button or an icon, for example, or anything - * that is visible to a user. Unlike {@link OO.ui.Widget widgets}, plain elements usually do not have events - * connected to them and can't be interacted with. - * - * @abstract - * @class - * - * @constructor - * @param {Object} [config] Configuration options - * @cfg {string[]} [classes] The names of the CSS classes to apply to the element. CSS styles are added - * to the top level (e.g., the outermost div) of the element. See the [OOjs UI documentation on MediaWiki][2] - * for an example. - * [2]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Buttons_and_Switches#cssExample - * @cfg {string} [id] The HTML id attribute used in the rendered tag. - * @cfg {string} [text] Text to insert - * @cfg {Array} [content] An array of content elements to append (after #text). - * Strings will be html-escaped; use an OO.ui.HtmlSnippet to append raw HTML. - * Instances of OO.ui.Element will have their $element appended. - * @cfg {jQuery} [$content] Content elements to append (after #text) - * @cfg {Mixed} [data] Custom data of any type or combination of types (e.g., string, number, array, object). - * Data can also be specified with the #setData method. - */ -OO.ui.Element = function OoUiElement( config ) { - // Configuration initialization - config = config || {}; - - // Properties - this.$ = $; - this.visible = true; - this.data = config.data; - this.$element = config.$element || - $( document.createElement( this.getTagName() ) ); - this.elementGroup = null; - this.debouncedUpdateThemeClassesHandler = this.debouncedUpdateThemeClasses.bind( this ); - this.updateThemeClassesPending = false; - - // Initialization - if ( Array.isArray( config.classes ) ) { - this.$element.addClass( config.classes.join( ' ' ) ); - } - if ( config.id ) { - this.$element.attr( 'id', config.id ); - } - if ( config.text ) { - this.$element.text( config.text ); - } - if ( config.content ) { - // The `content` property treats plain strings as text; use an - // HtmlSnippet to append HTML content. `OO.ui.Element`s get their - // appropriate $element appended. - this.$element.append( config.content.map( function ( v ) { - if ( typeof v === 'string' ) { - // Escape string so it is properly represented in HTML. - return document.createTextNode( v ); - } else if ( v instanceof OO.ui.HtmlSnippet ) { - // Bypass escaping. - return v.toString(); - } else if ( v instanceof OO.ui.Element ) { - return v.$element; - } - return v; - } ) ); - } - if ( config.$content ) { - // The `$content` property treats plain strings as HTML. - this.$element.append( config.$content ); - } -}; - -/* Setup */ - -OO.initClass( OO.ui.Element ); - -/* Static Properties */ - -/** - * The name of the HTML tag used by the element. - * - * The static value may be ignored if the #getTagName method is overridden. - * - * @static - * @inheritable - * @property {string} - */ -OO.ui.Element.static.tagName = 'div'; - -/* Static Methods */ - -/** - * Reconstitute a JavaScript object corresponding to a widget created - * by the PHP implementation. - * - * @param {string|HTMLElement|jQuery} idOrNode - * A DOM id (if a string) or node for the widget to infuse. - * @return {OO.ui.Element} - * The `OO.ui.Element` corresponding to this (infusable) document node. - * For `Tag` objects emitted on the HTML side (used occasionally for content) - * the value returned is a newly-created Element wrapping around the existing - * DOM node. - */ -OO.ui.Element.static.infuse = function ( idOrNode ) { - var obj = OO.ui.Element.static.unsafeInfuse( idOrNode, true ); - // Verify that the type matches up. - // FIXME: uncomment after T89721 is fixed (see T90929) - /* - if ( !( obj instanceof this['class'] ) ) { - throw new Error( 'Infusion type mismatch!' ); - } - */ - return obj; -}; - -/** - * Implementation helper for `infuse`; skips the type check and has an - * extra property so that only the top-level invocation touches the DOM. - * @private - * @param {string|HTMLElement|jQuery} idOrNode - * @param {boolean} top True only for top-level invocation. - * @return {OO.ui.Element} - */ -OO.ui.Element.static.unsafeInfuse = function ( idOrNode, top ) { - // look for a cached result of a previous infusion. - var id, $elem, data, cls, obj; - if ( typeof idOrNode === 'string' ) { - id = idOrNode; - $elem = $( document.getElementById( id ) ); - } else { - $elem = $( idOrNode ); - id = $elem.attr( 'id' ); - } - data = $elem.data( 'ooui-infused' ); - if ( data ) { - // cached! - if ( data === true ) { - throw new Error( 'Circular dependency! ' + id ); - } - return data; - } - if ( !$elem.length ) { - throw new Error( 'Widget not found: ' + id ); - } - data = $elem.attr( 'data-ooui' ); - if ( !data ) { - throw new Error( 'No infusion data found: ' + id ); - } - try { - data = $.parseJSON( data ); - } catch ( _ ) { - data = null; - } - if ( !( data && data._ ) ) { - throw new Error( 'No valid infusion data found: ' + id ); - } - if ( data._ === 'Tag' ) { - // Special case: this is a raw Tag; wrap existing node, don't rebuild. - return new OO.ui.Element( { $element: $elem } ); - } - cls = OO.ui[data._]; - if ( !cls ) { - throw new Error( 'Unknown widget type: ' + id ); - } - $elem.data( 'ooui-infused', true ); // prevent loops - data.id = id; // implicit - data = OO.copy( data, null, function deserialize( value ) { - if ( OO.isPlainObject( value ) ) { - if ( value.tag ) { - return OO.ui.Element.static.unsafeInfuse( value.tag, false ); - } - if ( value.html ) { - return new OO.ui.HtmlSnippet( value.html ); - } - } - } ); - // jscs:disable requireCapitalizedConstructors - obj = new cls( data ); // rebuild widget - // now replace old DOM with this new DOM. - if ( top ) { - $elem.replaceWith( obj.$element ); - } - obj.$element.data( 'ooui-infused', obj ); - // set the 'data-ooui' attribute so we can identify infused widgets - obj.$element.attr( 'data-ooui', '' ); - return obj; -}; - -/** - * Get a jQuery function within a specific document. - * - * @static - * @param {jQuery|HTMLElement|HTMLDocument|Window} context Context to bind the function to - * @param {jQuery} [$iframe] HTML iframe element that contains the document, omit if document is - * not in an iframe - * @return {Function} Bound jQuery function - */ -OO.ui.Element.static.getJQuery = function ( context, $iframe ) { - function wrapper( selector ) { - return $( selector, wrapper.context ); - } - - wrapper.context = this.getDocument( context ); - - if ( $iframe ) { - wrapper.$iframe = $iframe; - } - - return wrapper; -}; - -/** - * Get the document of an element. - * - * @static - * @param {jQuery|HTMLElement|HTMLDocument|Window} obj Object to get the document for - * @return {HTMLDocument|null} Document object - */ -OO.ui.Element.static.getDocument = function ( obj ) { - // jQuery - selections created "offscreen" won't have a context, so .context isn't reliable - return ( obj[ 0 ] && obj[ 0 ].ownerDocument ) || - // Empty jQuery selections might have a context - obj.context || - // HTMLElement - obj.ownerDocument || - // Window - obj.document || - // HTMLDocument - ( obj.nodeType === 9 && obj ) || - null; -}; - -/** - * Get the window of an element or document. - * - * @static - * @param {jQuery|HTMLElement|HTMLDocument|Window} obj Context to get the window for - * @return {Window} Window object - */ -OO.ui.Element.static.getWindow = function ( obj ) { - var doc = this.getDocument( obj ); - return doc.parentWindow || doc.defaultView; -}; - -/** - * Get the direction of an element or document. - * - * @static - * @param {jQuery|HTMLElement|HTMLDocument|Window} obj Context to get the direction for - * @return {string} Text direction, either 'ltr' or 'rtl' - */ -OO.ui.Element.static.getDir = function ( obj ) { - var isDoc, isWin; - - if ( obj instanceof jQuery ) { - obj = obj[ 0 ]; - } - isDoc = obj.nodeType === 9; - isWin = obj.document !== undefined; - if ( isDoc || isWin ) { - if ( isWin ) { - obj = obj.document; - } - obj = obj.body; - } - return $( obj ).css( 'direction' ); -}; - -/** - * Get the offset between two frames. - * - * TODO: Make this function not use recursion. - * - * @static - * @param {Window} from Window of the child frame - * @param {Window} [to=window] Window of the parent frame - * @param {Object} [offset] Offset to start with, used internally - * @return {Object} Offset object, containing left and top properties - */ -OO.ui.Element.static.getFrameOffset = function ( from, to, offset ) { - var i, len, frames, frame, rect; - - if ( !to ) { - to = window; - } - if ( !offset ) { - offset = { top: 0, left: 0 }; - } - if ( from.parent === from ) { - return offset; - } - - // Get iframe element - frames = from.parent.document.getElementsByTagName( 'iframe' ); - for ( i = 0, len = frames.length; i < len; i++ ) { - if ( frames[ i ].contentWindow === from ) { - frame = frames[ i ]; - break; - } - } - - // Recursively accumulate offset values - if ( frame ) { - rect = frame.getBoundingClientRect(); - offset.left += rect.left; - offset.top += rect.top; - if ( from !== to ) { - this.getFrameOffset( from.parent, offset ); - } - } - return offset; -}; - -/** - * Get the offset between two elements. - * - * The two elements may be in a different frame, but in that case the frame $element is in must - * be contained in the frame $anchor is in. - * - * @static - * @param {jQuery} $element Element whose position to get - * @param {jQuery} $anchor Element to get $element's position relative to - * @return {Object} Translated position coordinates, containing top and left properties - */ -OO.ui.Element.static.getRelativePosition = function ( $element, $anchor ) { - var iframe, iframePos, - pos = $element.offset(), - anchorPos = $anchor.offset(), - elementDocument = this.getDocument( $element ), - anchorDocument = this.getDocument( $anchor ); - - // If $element isn't in the same document as $anchor, traverse up - while ( elementDocument !== anchorDocument ) { - iframe = elementDocument.defaultView.frameElement; - if ( !iframe ) { - throw new Error( '$element frame is not contained in $anchor frame' ); - } - iframePos = $( iframe ).offset(); - pos.left += iframePos.left; - pos.top += iframePos.top; - elementDocument = iframe.ownerDocument; - } - pos.left -= anchorPos.left; - pos.top -= anchorPos.top; - return pos; -}; - -/** - * Get element border sizes. - * - * @static - * @param {HTMLElement} el Element to measure - * @return {Object} Dimensions object with `top`, `left`, `bottom` and `right` properties - */ -OO.ui.Element.static.getBorders = function ( el ) { - var doc = el.ownerDocument, - win = doc.parentWindow || doc.defaultView, - style = win && win.getComputedStyle ? - win.getComputedStyle( el, null ) : - el.currentStyle, - $el = $( el ), - top = parseFloat( style ? style.borderTopWidth : $el.css( 'borderTopWidth' ) ) || 0, - left = parseFloat( style ? style.borderLeftWidth : $el.css( 'borderLeftWidth' ) ) || 0, - bottom = parseFloat( style ? style.borderBottomWidth : $el.css( 'borderBottomWidth' ) ) || 0, - right = parseFloat( style ? style.borderRightWidth : $el.css( 'borderRightWidth' ) ) || 0; - - return { - top: top, - left: left, - bottom: bottom, - right: right - }; -}; - -/** - * Get dimensions of an element or window. - * - * @static - * @param {HTMLElement|Window} el Element to measure - * @return {Object} Dimensions object with `borders`, `scroll`, `scrollbar` and `rect` properties - */ -OO.ui.Element.static.getDimensions = function ( el ) { - var $el, $win, - doc = el.ownerDocument || el.document, - win = doc.parentWindow || doc.defaultView; - - if ( win === el || el === doc.documentElement ) { - $win = $( win ); - return { - borders: { top: 0, left: 0, bottom: 0, right: 0 }, - scroll: { - top: $win.scrollTop(), - left: $win.scrollLeft() - }, - scrollbar: { right: 0, bottom: 0 }, - rect: { - top: 0, - left: 0, - bottom: $win.innerHeight(), - right: $win.innerWidth() - } - }; - } else { - $el = $( el ); - return { - borders: this.getBorders( el ), - scroll: { - top: $el.scrollTop(), - left: $el.scrollLeft() - }, - scrollbar: { - right: $el.innerWidth() - el.clientWidth, - bottom: $el.innerHeight() - el.clientHeight - }, - rect: el.getBoundingClientRect() - }; - } -}; - -/** - * Get scrollable object parent - * - * documentElement can't be used to get or set the scrollTop - * property on Blink. Changing and testing its value lets us - * use 'body' or 'documentElement' based on what is working. - * - * https://code.google.com/p/chromium/issues/detail?id=303131 - * - * @static - * @param {HTMLElement} el Element to find scrollable parent for - * @return {HTMLElement} Scrollable parent - */ -OO.ui.Element.static.getRootScrollableElement = function ( el ) { - var scrollTop, body; - - if ( OO.ui.scrollableElement === undefined ) { - body = el.ownerDocument.body; - scrollTop = body.scrollTop; - body.scrollTop = 1; - - if ( body.scrollTop === 1 ) { - body.scrollTop = scrollTop; - OO.ui.scrollableElement = 'body'; - } else { - OO.ui.scrollableElement = 'documentElement'; - } - } - - return el.ownerDocument[ OO.ui.scrollableElement ]; -}; - -/** - * Get closest scrollable container. - * - * Traverses up until either a scrollable element or the root is reached, in which case the window - * will be returned. - * - * @static - * @param {HTMLElement} el Element to find scrollable container for - * @param {string} [dimension] Dimension of scrolling to look for; `x`, `y` or omit for either - * @return {HTMLElement} Closest scrollable container - */ -OO.ui.Element.static.getClosestScrollableContainer = function ( el, dimension ) { - var i, val, - props = [ 'overflow' ], - $parent = $( el ).parent(); - - if ( dimension === 'x' || dimension === 'y' ) { - props.push( 'overflow-' + dimension ); - } - - while ( $parent.length ) { - if ( $parent[ 0 ] === this.getRootScrollableElement( el ) ) { - return $parent[ 0 ]; - } - i = props.length; - while ( i-- ) { - val = $parent.css( props[ i ] ); - if ( val === 'auto' || val === 'scroll' ) { - return $parent[ 0 ]; - } - } - $parent = $parent.parent(); - } - return this.getDocument( el ).body; -}; - -/** - * Scroll element into view. - * - * @static - * @param {HTMLElement} el Element to scroll into view - * @param {Object} [config] Configuration options - * @param {string} [config.duration] jQuery animation duration value - * @param {string} [config.direction] Scroll in only one direction, e.g. 'x' or 'y', omit - * to scroll in both directions - * @param {Function} [config.complete] Function to call when scrolling completes - */ -OO.ui.Element.static.scrollIntoView = function ( el, config ) { - // Configuration initialization - config = config || {}; - - var rel, anim = {}, - callback = typeof config.complete === 'function' && config.complete, - sc = this.getClosestScrollableContainer( el, config.direction ), - $sc = $( sc ), - eld = this.getDimensions( el ), - scd = this.getDimensions( sc ), - $win = $( this.getWindow( el ) ); - - // Compute the distances between the edges of el and the edges of the scroll viewport - if ( $sc.is( 'html, body' ) ) { - // If the scrollable container is the root, this is easy - rel = { - top: eld.rect.top, - bottom: $win.innerHeight() - eld.rect.bottom, - left: eld.rect.left, - right: $win.innerWidth() - eld.rect.right - }; - } else { - // Otherwise, we have to subtract el's coordinates from sc's coordinates - rel = { - top: eld.rect.top - ( scd.rect.top + scd.borders.top ), - bottom: scd.rect.bottom - scd.borders.bottom - scd.scrollbar.bottom - eld.rect.bottom, - left: eld.rect.left - ( scd.rect.left + scd.borders.left ), - right: scd.rect.right - scd.borders.right - scd.scrollbar.right - eld.rect.right - }; - } - - if ( !config.direction || config.direction === 'y' ) { - if ( rel.top < 0 ) { - anim.scrollTop = scd.scroll.top + rel.top; - } else if ( rel.top > 0 && rel.bottom < 0 ) { - anim.scrollTop = scd.scroll.top + Math.min( rel.top, -rel.bottom ); - } - } - if ( !config.direction || config.direction === 'x' ) { - if ( rel.left < 0 ) { - anim.scrollLeft = scd.scroll.left + rel.left; - } else if ( rel.left > 0 && rel.right < 0 ) { - anim.scrollLeft = scd.scroll.left + Math.min( rel.left, -rel.right ); - } - } - if ( !$.isEmptyObject( anim ) ) { - $sc.stop( true ).animate( anim, config.duration || 'fast' ); - if ( callback ) { - $sc.queue( function ( next ) { - callback(); - next(); - } ); - } - } else { - if ( callback ) { - callback(); - } - } -}; - -/** - * Force the browser to reconsider whether it really needs to render scrollbars inside the element - * and reserve space for them, because it probably doesn't. - * - * Workaround primarily for <https://code.google.com/p/chromium/issues/detail?id=387290>, but also - * similar bugs in other browsers. "Just" forcing a reflow is not sufficient in all cases, we need - * to first actually detach (or hide, but detaching is simpler) all children, *then* force a reflow, - * and then reattach (or show) them back. - * - * @static - * @param {HTMLElement} el Element to reconsider the scrollbars on - */ -OO.ui.Element.static.reconsiderScrollbars = function ( el ) { - var i, len, nodes = []; - // Detach all children - while ( el.firstChild ) { - nodes.push( el.firstChild ); - el.removeChild( el.firstChild ); - } - // Force reflow - void el.offsetHeight; - // Reattach all children - for ( i = 0, len = nodes.length; i < len; i++ ) { - el.appendChild( nodes[ i ] ); - } -}; - -/* Methods */ - -/** - * Toggle visibility of an element. - * - * @param {boolean} [show] Make element visible, omit to toggle visibility - * @fires visible - * @chainable - */ -OO.ui.Element.prototype.toggle = function ( show ) { - show = show === undefined ? !this.visible : !!show; - - if ( show !== this.isVisible() ) { - this.visible = show; - this.$element.toggleClass( 'oo-ui-element-hidden', !this.visible ); - this.emit( 'toggle', show ); - } - - return this; -}; - -/** - * Check if element is visible. - * - * @return {boolean} element is visible - */ -OO.ui.Element.prototype.isVisible = function () { - return this.visible; -}; - -/** - * Get element data. - * - * @return {Mixed} Element data - */ -OO.ui.Element.prototype.getData = function () { - return this.data; -}; - -/** - * Set element data. - * - * @param {Mixed} Element data - * @chainable - */ -OO.ui.Element.prototype.setData = function ( data ) { - this.data = data; - return this; -}; - -/** - * Check if element supports one or more methods. - * - * @param {string|string[]} methods Method or list of methods to check - * @return {boolean} All methods are supported - */ -OO.ui.Element.prototype.supports = function ( methods ) { - var i, len, - support = 0; - - methods = Array.isArray( methods ) ? methods : [ methods ]; - for ( i = 0, len = methods.length; i < len; i++ ) { - if ( $.isFunction( this[ methods[ i ] ] ) ) { - support++; - } - } - - return methods.length === support; -}; - -/** - * Update the theme-provided classes. - * - * @localdoc This is called in element mixins and widget classes any time state changes. - * Updating is debounced, minimizing overhead of changing multiple attributes and - * guaranteeing that theme updates do not occur within an element's constructor - */ -OO.ui.Element.prototype.updateThemeClasses = function () { - if ( !this.updateThemeClassesPending ) { - this.updateThemeClassesPending = true; - setTimeout( this.debouncedUpdateThemeClassesHandler ); - } -}; - -/** - * @private - */ -OO.ui.Element.prototype.debouncedUpdateThemeClasses = function () { - OO.ui.theme.updateElementClasses( this ); - this.updateThemeClassesPending = false; -}; - -/** - * Get the HTML tag name. - * - * Override this method to base the result on instance information. - * - * @return {string} HTML tag name - */ -OO.ui.Element.prototype.getTagName = function () { - return this.constructor.static.tagName; -}; - -/** - * Check if the element is attached to the DOM - * @return {boolean} The element is attached to the DOM - */ -OO.ui.Element.prototype.isElementAttached = function () { - return $.contains( this.getElementDocument(), this.$element[ 0 ] ); -}; - -/** - * Get the DOM document. - * - * @return {HTMLDocument} Document object - */ -OO.ui.Element.prototype.getElementDocument = function () { - // Don't cache this in other ways either because subclasses could can change this.$element - return OO.ui.Element.static.getDocument( this.$element ); -}; - -/** - * Get the DOM window. - * - * @return {Window} Window object - */ -OO.ui.Element.prototype.getElementWindow = function () { - return OO.ui.Element.static.getWindow( this.$element ); -}; - -/** - * Get closest scrollable container. - */ -OO.ui.Element.prototype.getClosestScrollableElementContainer = function () { - return OO.ui.Element.static.getClosestScrollableContainer( this.$element[ 0 ] ); -}; - -/** - * Get group element is in. - * - * @return {OO.ui.GroupElement|null} Group element, null if none - */ -OO.ui.Element.prototype.getElementGroup = function () { - return this.elementGroup; -}; - -/** - * Set group element is in. - * - * @param {OO.ui.GroupElement|null} group Group element, null if none - * @chainable - */ -OO.ui.Element.prototype.setElementGroup = function ( group ) { - this.elementGroup = group; - return this; -}; - -/** - * Scroll element into view. - * - * @param {Object} [config] Configuration options - */ -OO.ui.Element.prototype.scrollElementIntoView = function ( config ) { - return OO.ui.Element.static.scrollIntoView( this.$element[ 0 ], config ); -}; |