Santhosh has uploaded a new change for review. https://gerrit.wikimedia.org/r/196574
Change subject: [WIP] A generic callout widget ...................................................................... [WIP] A generic callout widget Change-Id: I0c6bc6b24b2ca903733bed2a4955741d46dc9ea6 --- A modules/widgets/callout/ext.cx.callout.css A modules/widgets/callout/ext.cx.callout.js 2 files changed, 476 insertions(+), 0 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/ContentTranslation refs/changes/74/196574/1 diff --git a/modules/widgets/callout/ext.cx.callout.css b/modules/widgets/callout/ext.cx.callout.css new file mode 100644 index 0000000..6d5efe4 --- /dev/null +++ b/modules/widgets/callout/ext.cx.callout.css @@ -0,0 +1,209 @@ +.cx-callout { + color: #333; + position: absolute; + overflow: visible; + padding: 20px; + background: white; + border: 1px solid #ccc; + border-bottom-width: 3px; + border-radius: 3px; + max-width: 500px; + z-index: 100000; + cursor: default; +} + +.cx-callout-w::after, .cx-callout-w::before { + right: 100%; + top: 50%; + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; +} + +.cx-callout-w::after { + border-color: transparent; + border-right-color: #fff; + border-width: 12px; + margin-top: -12px; +} +.cx-callout-w::before { + border-color: transparent; + border-right-color: #ccc; + border-width: 13px; + margin-top: -13px; +} + + +.cx-callout-e:after, .cx-callout-e:before { + left: 100%; + top: 50%; + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; +} + +.cx-callout-e:after { + border-color: transparent; + border-left-color: #fff; + border-width: 12px; + margin-top: -12px; +} +.cx-callout-e:before { + border-color: transparent; + border-left-color: #ccc; + border-width: 13px; + margin-top: -13px; +} + + +.cx-callout-n:after, .cx-callout-n:before { + bottom: 100%; + left: 50%; + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; +} + +.cx-callout-n:after { + border-color: transparent; + border-bottom-color: #fff; + border-width: 12px; + margin-left: -12px; +} +.cx-callout-n:before { + border-color: transparent; + border-bottom-color: #ccc; + border-width: 13px; + margin-left: -13px; +} + +.cx-callout-nw:after, .cx-callout-nw:before { + bottom: 100%; + left: 25%; + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; +} + +.cx-callout-nw:after { + border-color: transparent; + border-bottom-color: #fff; + border-width: 12px; + margin-left: -12px; +} + +.cx-callout-nw:before { + border-color: transparent; + border-bottom-color: #ccc; + border-width: 13px; + margin-left: -13px; +} + +.cx-callout-ne:after, .cx-callout-ne:before { + bottom: 100%; + left: 75%; + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; +} + +.cx-callout-ne:after { + border-color: transparent; + border-bottom-color: #fff; + border-width: 12px; + margin-left: -12px; +} +.cx-callout-ne:before { + border-color: transparent; + border-bottom-color: #ccc; + border-width: 13px; + margin-left: -13px; +} + +.cx-callout-s:after, .cx-callout-s:before { + top: 100%; + left: 50%; + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; +} + +.cx-callout-s:after { + border-color: transparent; + border-top-color: #fff; + border-width: 12px; + margin-left: -12px; +} +.cx-callout-s:before { + border-color: transparent; + border-top-color: #ccc; + border-width: 13px; + margin-left: -13px; +} + +.cx-callout-sw:after, .cx-callout-sw:before { + top: 100%; + left: 25%; + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; +} + +.cx-callout-sw:after { + border-color: transparent; + border-top-color: #fff; + border-width: 12px; + margin-left: -12px; +} +.cx-callout-sw:before { + border-color: transparent; + border-top-color: #ccc; + border-width: 13px; + margin-left: -13px; +} + +.cx-callout-se:after, .cx-callout-se:before { + top: 100%; + left: 75%; + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; +} + +.cx-callout-se:after { + border-color: transparent; + border-top-color: #fff; + border-width: 12px; + margin-left: -12px; +} + +.cx-callout-se:before { + border-color: transparent; + border-top-color: #ccc; + border-width: 13px; + margin-left: -13px; +} diff --git a/modules/widgets/callout/ext.cx.callout.js b/modules/widgets/callout/ext.cx.callout.js new file mode 100644 index 0000000..298d771 --- /dev/null +++ b/modules/widgets/callout/ext.cx.callout.js @@ -0,0 +1,267 @@ +/** + * @file + * @ingroup Extensions + * @copyright See AUTHORS.txt + * @license GPL-2.0+ + */ +( function ( $ ) { + 'use strict'; + + function maybeCall( thing, ctx ) { + return ( typeof thing === 'function' ) ? ( thing.call( ctx ) ) : thing; + } + + function Callout( element, options ) { + this.$element = $( element ); + this.options = $.extend( {}, $.fn.callout.defaults, options ); + this.shown = false; + this.listen(); + } + + Callout.prototype.show = function () { + var $dialog, content; + + content = maybeCall( this.options.content ); + if ( !content ) { + return; + } + + $dialog = this.dialog(); + $dialog.find( '.cx-callout-content' ).html( content ); + $dialog[ 0 ].className = 'cx-callout'; // reset classname in case of dynamic gravity + + $dialog.remove().css( { + top: 0, + left: 0, + visibility: 'hidden', + display: 'block' + } ).appendTo( document.body ); + + this.position(); + + if ( this.options.fade ) { + $dialog.stop().css( { + opacity: 0, + display: 'block', + visibility: 'visible' + } ).animate( { + opacity: this.options.opacity + }, 100 ); + } else { + $dialog.css( { + visibility: 'visible', + opacity: this.options.opacity + } ); + } + this.shown = true; + }; + + Callout.prototype.position = function () { + var $dialog, pos, gravity, actualWidth, actualHeight, position; + + pos = $.extend( {}, this.$element.offset(), { + width: this.$element[ 0 ].offsetWidth, + height: this.$element[ 0 ].offsetHeight + } ); + + $dialog = this.dialog(); + gravity = ( typeof this.options.gravity === 'function' ) ? this.options.gravity.call( this.$element[ 0 ] ) : this.options.gravity; + + // Attach css classes before checking height/width so they + // can be applied. + $dialog.addClass( 'cx-callout-' + gravity ); + + actualWidth = $dialog[ 0 ].offsetWidth; + actualHeight = $dialog[ 0 ].offsetHeight; + switch ( gravity.charAt( 0 ) ) { + case 'n': + position = { + top: pos.top + pos.height + this.options.offset, + left: pos.left + pos.width / 2 - actualWidth / 2 + }; + break; + case 's': + position = { + top: pos.top - actualHeight - this.options.offset, + left: pos.left + pos.width / 2 - actualWidth / 2 + }; + break; + case 'e': + position = { + top: pos.top + pos.height / 2 - actualHeight / 2, + left: pos.left - actualWidth - this.options.offset + }; + break; + case 'w': + position = { + top: pos.top + pos.height / 2 - actualHeight / 2, + left: pos.left + pos.width + this.options.offset + }; + break; + } + + if ( gravity.length === 2 ) { + if ( gravity.charAt( 1 ) === 'w' ) { + if ( this.options.center ) { + position.left = pos.left + pos.width / 2 - 15; + } else { + position.left = pos.left; + } + } else { + if ( this.options.center ) { + position.left = pos.left + pos.width / 2 - actualWidth + 15; + } else { + position.left = pos.left + pos.width; + } + } + } + $dialog.css( position ); + }; + + Callout.prototype.listen = function () { + var timer, self = this; + + if ( this.options.trigger === 'hover' ) { + this.$element.hover( function () { + self.show(); + }, function () { + self.hide(); + } ); + } + if ( this.options.trigger === 'click' ) { + this.$element.on( 'click', function () { + if ( self.shown ) { + self.hide(); + } else { + self.show(); + } + } ); + } + $( window ).resize( function () { + clearTimeout( timer ); + timer = setTimeout( function () { + self.position(); + }, 200 ); + } ); + }; + + Callout.prototype.hide = function () { + if ( this.options.fade ) { + this.dialog().stop().fadeOut( 100, function () { + $( this ).remove(); + } ); + } else { + this.dialog().remove(); + } + this.shown = false; + }; + + Callout.prototype.dialog = function () { + if ( !this.$dialog ) { + this.$dialog = $( '<div class="cx-callout"></div>' ).html( '</div><div class="cx-callout-content"></div>' ); + } + return this.$dialog; + }; + + Callout.prototype.validate = function () { + if ( !this.$element[ 0 ].parentNode ) { + this.hide(); + this.$element = null; + this.options = null; + } + }; + + $.fn.callout = function ( options ) { + if ( options === true ) { + return this.data( 'callout' ); + } else if ( typeof options === 'string' ) { + var callout = this.data( 'callout' ); + if ( callout ) { + callout[ options ](); + } + return this; + } + + function get( ele ) { + var callout = $.data( ele, 'callout' ); + if ( !callout ) { + callout = new Callout( ele, $.fn.callout.elementOptions( ele, options ) ); + $.data( ele, 'callout', callout ); + } + return callout; + } + + this.each( function () { + get( this ); + } ); + + return this; + }; + + $.fn.callout.defaults = { + gravity: 'n', + center: true, + offset: 10, + opacity: 1.0, + trigger: 'hover' + }; + + // Overwrite this method to provide options on a per-element basis. + // For example, you could store the gravity in a 'cx-callout-gravity' attribute: + // return $.extend({}, options, {gravity: $(ele).attr('cx-callout-gravity') || 'n' }); + // (remember - do not modify 'options' in place!) + $.fn.callout.elementOptions = function ( ele, options ) { + return $.metadata ? $.extend( {}, options, $( ele ).metadata() ) : options; + }; + + $.fn.callout.autoNS = function () { + return $( this ).offset().top > ( $( document ).scrollTop() + $( window ).height() / 2 ) ? 's' : 'n'; + }; + + $.fn.callout.autoWE = function () { + return $( this ).offset().left > ( $( document ).scrollLeft() + $( window ).width() / 2 ) ? 'e' : 'w'; + }; + + /** + * yields a closure of the supplied parameters, producing a function that takes + * no arguments and is suitable for use as an autogravity function like so: + * + * @param margin (int) - distance from the viewable region edge that an + * element should be before setting its tooltip's gravity to be away + * from that edge. + * @param prefer (string, e.g. 'n', 'sw', 'w') - the direction to prefer + * if there are no viewable region edges effecting the tooltip's + * gravity. It will try to vary from this minimally, for example, + * if 'sw' is preferred and an element is near the right viewable + * region edge, but not the top edge, it will set the gravity for + * that element's tooltip to be 'se', preserving the southern + * component. + */ + $.fn.callout.autoBounds = function ( margin, prefer ) { + return function () { + var dir = { + ns: prefer[ 0 ], + ew: ( prefer.length > 1 ? prefer[ 1 ] : false ) + }, + boundTop = $( document ).scrollTop() + margin, + boundLeft = $( document ).scrollLeft() + margin, + $this = $( this ); + + if ( $this.offset().top < boundTop ) { + dir.ns = 'n'; + } + if ( $this.offset().left < boundLeft ) { + dir.ew = 'w'; + } + if ( $( window ).width() + $( document ).scrollLeft() - $this.offset().left < margin ) { + dir.ew = 'e'; + } + if ( $( window ).height() + $( document ).scrollTop() - $this.offset().top < margin ) { + dir.ns = 's'; + } + + return dir.ns + ( dir.ew ? dir.ew : '' ); + }; + }; + +}( jQuery ) ); -- To view, visit https://gerrit.wikimedia.org/r/196574 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I0c6bc6b24b2ca903733bed2a4955741d46dc9ea6 Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/ContentTranslation Gerrit-Branch: master Gerrit-Owner: Santhosh <santhosh.thottin...@gmail.com> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits