Added: labs/panopticon/src/asf/panopticon/static/js/user-popover.js URL: http://svn.apache.org/viewvc/labs/panopticon/src/asf/panopticon/static/js/user-popover.js?rev=1523510&view=auto ============================================================================== --- labs/panopticon/src/asf/panopticon/static/js/user-popover.js (added) +++ labs/panopticon/src/asf/panopticon/static/js/user-popover.js Sun Sep 15 22:06:29 2013 @@ -0,0 +1,223 @@ +(function( $ ) { + + var box_intersection = function(a, b) { + var x = { + top: Math.max(a.top, b.top), + bottom: Math.min(a.bottom, b.bottom), + left: Math.max(a.left, b.left), + right: Math.min(a.right, b.right) + }; + + return (x.top < x.bottom && x.left < x.right) ? x : null; + }; + + + var box_area = function(box) { + if (!box) return 0; + + var width = box.right - box.left, + height = box.bottom - box.top; + + return width * height; + }; + + + var screen_bounds = function() { + var box = { + top: $(document).scrollTop(), + left: $(document).scrollLeft() + }; + + box.bottom = box.top + $(window).height(); + box.right = box.left + $(window).width(); + + return box; + }; + + + var auto_placement = function(tip, element) { + // make tip positioned so that we can get its dimensions + $(tip) + .remove() + .css({ top: 0, left: 0, display: 'block' }) + .appendTo(document.body) + + var screen_box = screen_bounds(), + best_placement = 'bottom', + best_visible_area = 0, + tip_width = tip.offsetWidth, + tip_height = tip.offsetHeight, + pos = this.getPosition(), + tip_boxes = { + bottom: {top: pos.top + pos.height, left: pos.left + pos.width / 2 - tip_width / 2}, + right: {top: pos.top + pos.height / 2 - tip_height / 2, left: pos.left + pos.width}, + top: {top: pos.top - tip_height, left: pos.left + pos.width / 2 - tip_width / 2}, + left: {top: pos.top + pos.height / 2 - tip_height / 2, left: pos.left - tip_width} + }, + placement_preference = ['bottom', 'right', 'top', 'left']; + + $.each(placement_preference, function(idx, placement) { + var tip_box = tip_boxes[placement], + intersection, + visible_area; + + tip_box.right = tip_box.left + tip_width; + tip_box.bottom = tip_box.top + tip_height; + + intersection = box_intersection(screen_box, tip_box); + visible_area = box_area(intersection); + + if (visible_area > best_visible_area) { + best_visible_area = visible_area; + best_placement = placement; + } + }); + + return best_placement; + }; + + + /* Mix in clickability and auto positioning options to Popover class + * ================================================================= */ + var Popover = $.fn.popover.Constructor; + + $.extend(Popover.prototype, { + + _show: Popover.prototype.show + , show: function() { + var $tip; + this.hoverState = 'in'; + this._show(); + $tip = this.tip(); + + // workaround for bootstrap 2.2 bug + // reattach the tooltip to the parent container so that it does not + // inherit CSS styles from the location of the link + if (this.options.container) { + var offset = $tip.offset(); + $tip + .detach() + .appendTo(this.options.container) + .offset(offset); + } + + if (this.options.clickable) { + $tip.mouseenter($.proxy(this.tipEnter, this)) + .mouseleave($.proxy(this.tipLeave, this)); + } + } + + , _getOptions: Popover.prototype.getOptions + , getOptions: function(options) { + options = this._getOptions(options); + + if (options.placement == 'auto') { + options.placement = auto_placement; + } + + if (options.clickable) { + if (typeof options.clickable == 'number') { + options.delay = { show: 1, hide: options.clickable }; + } else if (!options.delay || !options.delay.hide) { + // if no hide delay is set we can't make it clickable + options.clickable = false; + } else if (!options.delay.show) { + // need a show delay of 1 or the parent code doesn't set the + // hoverState needed to make clickability work + options.delay.show = 1; + } + } + + return options; + } + + , tipEnter: function () { + this.hoverState = 'in' + } + + , tipLeave: function () { + var self = this; + + self.hoverState = 'out'; + setTimeout(function() { + if (self.hoverState == 'out') { + self.hide(); + } + }, self.options.delay.hide); + } + + }); + + + $.fn.async_popover = function(options, callback) { + this.live('mouseover', function() { + var $this = $(this), + popover = $this.data('popover'); + + // display any existing popover + if (popover) { + popover.show(); + return; + } + + // initialize a new popover + $this.popover(options); + popover = $this.data('popover'); + + // set up popover loading state + $this.attr('data-original-title', 'Loading...'); + popover.hoverState = 'loading'; + + // Only show the loading state if we don't get a timely reponse. + // When the data loads quickly this is less jarring than showing the + // loading state and then immediately reloading with the real data. + setTimeout(function() { + if (popover.hoverState == 'loading') { + popover.show(); + } + }, 300); + + callback.call(this, function(title, content) { + $this.attr('data-original-title', title); + $this.attr('data-content', content); + if (popover.hoverState == 'loading' || popover.hoverState == 'in') { + popover.hoverState = 'in'; + popover.show(); + } + }); + + }); + + return this; + }; + + + $.fn.user_popover = function(url, template, options) { + var cache = {}; + + return this.async_popover(options, function(callback) { + var username = $(this).data('username'), + data = cache[username]; + + // used cached user data if possible + if (data) { + callback(data.title, data.content); + return; + } + + var set = function(title, content) { + cache[username] = { title: title, content: content }; + callback(title, content); + }; + + $.getJSON(url + username) + .success(function(data) { + set(data.fullname, template(data)); + }) + .error(function(xhr, status) { + set('Error', 'Error fetching user information.'); + }); + }); + }; + +})( window.jQuery );
Modified: labs/panopticon/src/asf/panopticon/templates/layout.html URL: http://svn.apache.org/viewvc/labs/panopticon/src/asf/panopticon/templates/layout.html?rev=1523510&r1=1523509&r2=1523510&view=diff ============================================================================== --- labs/panopticon/src/asf/panopticon/templates/layout.html (original) +++ labs/panopticon/src/asf/panopticon/templates/layout.html Sun Sep 15 22:06:29 2013 @@ -75,7 +75,7 @@ <p class="navbar-text pull-right"> Logged in as <a href="#" rel="user" - data-username="{{ g.identity.id }}" + data-username="{{ g.identity.name }}" >{{ g.identity.name }}</a> | <a href="{{ url_for('logout') }}">Sign out</a> </p> @@ -113,7 +113,29 @@ </p> </div> </div> -<script src="//code.jquery.com/jquery.js"></script> +<script src="//code.jquery.com/jquery-1.8.3.js"></script> <script src="{{ url_for('static', filename='js/bootstrap.min.js') }}"></script> +<script src="{{ url_for('static', filename='js/handlebars.js') }}"></script> +<script src="{{ url_for('static', filename='js/user-popover.js') }}"></script> + +<script id="user-info-template" type="text/x-handlebars-template"> + {% raw %} + <img class="popup-img" src="{{ picture_url }}"> + <div class="popup-address"> + <a href="mailto:{{ email_string }}">{{ email }}</a> + </div> + {% endraw %} +</script> + +<script> + $(function() { + $('a[rel=user]').user_popover( + {{ url_for('user_info', username='')|tojson|safe }}, + Handlebars.compile($('#user-info-template').html()), + { placement: 'auto', html: true, clickable: 300, trigger: 'hover', + container: 'body' } + ); + }); +</script> </body> </html> --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
