diff --git a/lib/galaxy/webapps/galaxy/controllers/visualization.py b/lib/galaxy/webapps/galaxy/controllers/visualization.py index fca5922cddd..0e963a693dd 100644 --- a/lib/galaxy/webapps/galaxy/controllers/visualization.py +++ b/lib/galaxy/webapps/galaxy/controllers/visualization.py @@ -745,47 +745,58 @@ class VisualizationController( BaseUIController, SharableMixin, UsesAnnotations, @web.expose @web.require_login() - def trackster(self, trans, id=None, **kwargs): + def trackster(self, trans, **kwargs): """ Display browser for the visualization denoted by id and add the datasets listed in `dataset_ids`. """ - # Get dataset to add. + # define app configuration + app = { 'jscript' : "viz/trackster" } + + # get dataset to add + id = kwargs.get( "id", None ) + + # get dataset to add new_dataset_id = kwargs.get( "dataset_id", None ) - # Check for gene region - gene_region = GenomeRegion.from_str(kwargs.get("gene_region", "")) - - # Set up new browser if no id provided. + # set up new browser if no id provided if not id: - # Use dbkey from dataset to be added or from incoming parameter. + # use dbkey from dataset to be added or from incoming parameter dbkey = None if new_dataset_id: dbkey = self.get_dataset( trans, new_dataset_id ).dbkey if dbkey == '?': dbkey = kwargs.get( "dbkey", None ) - - # fill template - return trans.fill_template( "tracks/browser.mako", viewport_config=gene_region.__dict__, add_dataset=new_dataset_id, default_dbkey=dbkey ) + + # save database key + app['default_dbkey'] = dbkey + + # add url + app['new_browser'] = web.url_for( controller='visualization', action='new_browser', default_dbkey=dbkey ) + else: + # load saved visualization + vis = self.get_visualization( trans, id, check_ownership=False, check_accessible=True ) + app['viz_config'] = self.get_visualization_config( trans, vis ) + + # backup id + app['id'] = id; - # Display saved visualization. - vis = self.get_visualization( trans, id, check_ownership=False, check_accessible=True ) - viz_config = self.get_visualization_config( trans, vis ) - - # Update gene region of saved visualization if user parses a new gene region in the url + # add dataset id + app['add_dataset'] = new_dataset_id + + # check for gene region + gene_region = GenomeRegion.from_str(kwargs.get("gene_region", "")) + + # update gene region of saved visualization if user parses a new gene region in the url if gene_region.chrom is not None: - viz_config['viewport']['chrom'] = gene_region.chrom - viz_config['viewport']['start'] = gene_region.start - viz_config['viewport']['end'] = gene_region.end - - ''' - FIXME: - if new_dataset is not None: - if trans.security.decode_id(new_dataset) in [ d["dataset_id"] for d in viz_config.get("tracks") ]: - new_dataset = None # Already in browser, so don't add - ''' + app['gene_region'] = { + 'chrom' : gene_region.chrom, + 'start' : gene_region.start, + 'end' : gene_region.end + } + # fill template - return trans.fill_template( 'tracks/browser.mako', config=viz_config, add_dataset=new_dataset_id ) + return trans.fill_template('galaxy.panels.mako', config = {'right_panel' : True, 'app' : app}) @web.expose def circster( self, trans, id=None, hda_ldda=None, dataset_id=None, dbkey=None ): @@ -839,7 +850,15 @@ class VisualizationController( BaseUIController, SharableMixin, UsesAnnotations, if not isinstance( genome_data, str ): track[ 'preloaded_data' ] = genome_data - return trans.fill_template( 'visualization/circster.mako', viz_config=viz_config, genome=genome ) + # define app configuration for generic mako template + app = { + 'jscript' : "viz/circster", + 'viz_config' : viz_config, + 'genome' : genome + } + + # fill template + return trans.fill_template('galaxy.panels.mako', config = {'app' : app}) @web.expose def sweepster( self, trans, id=None, hda_ldda=None, dataset_id=None, regions=None ): diff --git a/static/scripts/galaxy.frame.js b/static/scripts/galaxy.frame.js index 99f0fca9f7c..6342d0cc0a7 100644 --- a/static/scripts/galaxy.frame.js +++ b/static/scripts/galaxy.frame.js @@ -4,7 +4,6 @@ // dependencies define(["utils/galaxy.css", "libs/backbone/backbone-relational"], function(css) { - // frame manager var GalaxyFrameManager = Backbone.View.extend( diff --git a/static/scripts/libs/farbtastic.js b/static/scripts/libs/farbtastic.js index 06882ddd8cd..7a495b844a3 100644 --- a/static/scripts/libs/farbtastic.js +++ b/static/scripts/libs/farbtastic.js @@ -98,7 +98,7 @@ $._farbtastic = function (container, options) { .find('div>*').css('position', 'absolute'); // IE Fix: Recreate canvas elements with doc.createElement and excanvas. - $.browser.msie && $('canvas', container).each(function () { + navigator.userAgent.match(/msie/i) && $('canvas', container).each(function () { // Fetch info. var attr = { 'class': $(this).attr('class'), style: this.getAttribute('style') }, e = document.createElement('canvas'); @@ -166,7 +166,7 @@ $._farbtastic = function (container, options) { // New color color2 = fb.pack(fb.HSLToRGB([d2, 1, 0.5])); if (i > 0) { - if ($.browser.msie) { + if (navigator.userAgent.match(/msie/i)) { // IE's gradient calculations mess up the colors. Correct along the diagonals. var corr = (1 + Math.min(Math.abs(Math.tan(angle1)), Math.abs(Math.tan(Math.PI / 2 - angle1)))) / n; color1 = fb.pack(fb.HSLToRGB([d1 - 0.15 * corr, 1, 0.5])); @@ -246,7 +246,7 @@ $._farbtastic = function (container, options) { fb.ctxMask.drawImage(buffer, 0, 0, sz + 1, sz + 1, -sq, -sq, sq * 2, sq * 2); } // Method #2: drawing commands (old Canvas). - else if (!$.browser.msie) { + else if (!navigator.userAgent.match(/msie/i)) { // Render directly at half-resolution var sz = Math.floor(size / 2); calculateMask(sz, sz, function (x, y, c, a) { diff --git a/static/scripts/libs/jquery/jquery.event.hover.js b/static/scripts/libs/jquery/jquery.event.hover.js index b2596f821d7..c607990ef59 100644 --- a/static/scripts/libs/jquery/jquery.event.hover.js +++ b/static/scripts/libs/jquery/jquery.event.hover.js @@ -1,20 +1,19 @@ ;(function($){ // secure $ jQuery alias /*******************************************************************************************/ -// jquery.event.hover.js - rev 5 +// jquery.event.hover.js // Copyright (c) 2008, Three Dub Media (http://threedubmedia.com) -// Liscensed under the MIT License (MIT-LICENSE.txt) +// Licensed under the MIT License (MIT-LICENSE.txt) // http://www.opensource.org/licenses/mit-license.php -// Created: 2008-06-02 | Updated: 2008-07-30 +// +// JQuery 1.9+ compatible version +// +// Optional settings : +// $.event.special.hover.delay = 100; +// Defines the delay (msec) while mouse is inside the element before checking the speed +// $.event.special.hover.speed = 100; +// Defines the maximum speed (px/sec) the mouse may be moving to trigger the hover event /*******************************************************************************************/ -// USE THESE PROPERTIES TO CUSTOMIZE SETTINGS... - -// $.event.special.hover.delay = 100; -// Defines the delay (msec) while mouse is inside the element before checking the speed - -// $.event.special.hover.speed = 100; -// Defines the maximum speed (px/sec) the mouse may be moving to trigger the hover event - // save the old jquery "hover" method $.fn._hover = $.fn.hover; @@ -47,8 +46,8 @@ function hoverHandler( event ){ data.dist2 = 0; // init mouse distance² data.event = event; // store the event event.type = "hoverstart"; // hijack event - if ( $.event.handle.call( this, event ) !== false ){ // handle "hoverstart" - data.elem = this; // ref to the current element + if($.event.dispatch.call(this, event) !== false) { + data.elem = this; // ref to the current element $.event.add( this, "mousemove", hoverHandler, data ); // track the mouse data.timer = setTimeout( compare, data.delay ); // start async compare } @@ -62,7 +61,7 @@ function hoverHandler( event ){ clearTimeout( data.timer ); // uncompare if ( data.hovered ){ event.type = "hoverend"; // hijack event - $.event.handle.call( this, event ); // handle "hoverend" + $.event.dispatch.call(this, event); // handle "hoverend" data.hovered--; // reset flag } else $.event.remove( data.elem, "mousemove", hoverHandler ); // untrack @@ -71,7 +70,7 @@ function hoverHandler( event ){ if ( data.dist2 <= Math.pow( data.speed*( data.delay/1e3 ), 2 ) ){ // speed acceptable $.event.remove( data.elem, "mousemove", hoverHandler ); // untrack data.event.type = "hover"; // hijack event - if ( $.event.handle.call( data.elem, data.event ) !== false ) // handle "hover" + if($.event.dispatch.call(data.elem, data.event) !== false) // handle "hover" data.hovered++; // flag for "hoverend" } else data.timer = setTimeout( compare, data.delay ); // async recurse diff --git a/static/scripts/mvc/ui.js b/static/scripts/mvc/ui.js index eb8602f2d49..59ab82c621c 100644 --- a/static/scripts/mvc/ui.js +++ b/static/scripts/mvc/ui.js @@ -1,101 +1,172 @@ /** - * -- Functions for creating large UI elements. -- + * necessary galaxy paths */ -// ============================================================================= -/** - * -- Utility models and views for Galaxy objects. -- - */ - -/** - * Clickable button represented as an icon. - */ -var IconButton = Backbone.Model.extend({ - defaults: { - title: "", - icon_class: "", - on_click: null, - menu_options: null, - tooltip_config: {}, - - isMenuButton : true, - id : null, - href : null, - target : null, - enabled : true, - visible : true +var GalaxyPaths = Backbone.Model.extend( +{ + defaults: + { + root_path: "", + image_path: "" } - - //validate : function( attributes ){ - //TODO: validate href or on_click - //TODO: validate icon_class - //} }); +/** + * functions for creating large ui elements + */ /** - * + * backbone model for icon buttons */ -var IconButtonView = Backbone.View.extend({ - - initialize : function(){ - // better rendering this way (for me anyway) +var IconButton = Backbone.Model.extend( +{ + defaults: + { + title : "", + icon_class : "", + on_click : null, + menu_options : null, + is_menu_button : true, + id : null, + href : null, + target : null, + enabled : true, + visible : true, + tooltip_config : {} + } +}); + +/** + * backbone view for icon buttons + */ +var IconButtonView = Backbone.View.extend( +{ + // initialize + initialize: function() + { + // better rendering this way this.model.attributes.tooltip_config = { placement : 'bottom' }; - this.model.bind( 'change', this.render, this ); + this.model.bind('change', this.render, this); }, - render : function(){ - //NOTE: not doing this hide will lead to disappearing buttons when they're both being hovered over & rendered - this.$el.tooltip( 'hide' ); + // render + render: function() + { + // hide tooltip + this.$el.tooltip('hide'); - // template in common-templates.html - var newElem = $( Handlebars.partials.iconButton( this.model.toJSON() ) ); - newElem.tooltip( this.model.get( 'tooltip_config' ) ); + // create element + var new_elem = this.template(this.model.attributes); - this.$el.replaceWith( newElem ); - this.setElement( newElem ); + // configure tooltip + new_elem.tooltip(this.model.get('tooltip_config')); + + // replace + this.$el.replaceWith(new_elem); + this.setElement(new_elem); + // return return this; }, - events : { + // events + events: + { 'click' : 'click' }, - click : function( event ){ + // click + click: function( event ) + { // if on_click pass to that function - if( this.model.attributes.on_click ){ - this.model.attributes.on_click( event ); + if(this.model.attributes.on_click) + { + this.model.attributes.on_click(event); return false; } + // otherwise, bubble up (to href or whatever) return true; + }, + + // generate html element + template: function(options) + { + // initialize + var buffer = 'title="' + options.title + '" class="icon-button'; + + // is menu button + if(options.is_menu_button) + buffer += ' menu-button'; + + // define tooltip + if(options.title) + buffer += ' tooltip'; + + // add icon class + buffer += ' ' + options.icon_class; + + // add enabled/disabled class + if(!options.enabled) + buffer += '_disabled'; + + // close class tag + buffer += '"'; + + // add id + if(options.id) + buffer += ' id="' + options.id + '"'; + + // add href + buffer += ' href="' + options.href + '"'; + + // add target for href + if(options.target) + buffer += ' target="' + options.target + '"'; + + // set visibility + if(!options.visible) + buffer += ' style="display: none;"'; + + // enabled/disabled + if (options.enabled) + buffer = ''; + else + buffer = ''; + + // return element + return $(buffer); } }); -//TODO: bc h.templates is gen. loaded AFTER ui, Handlebars.partials.iconButton === undefined -IconButtonView.templates = { - iconButton : Handlebars.partials.iconButton -}; -var IconButtonCollection = Backbone.Collection.extend({ +// define collection +var IconButtonCollection = Backbone.Collection.extend( +{ model: IconButton }); - -//------------------------------------------------------------------------------ /** - * Menu with multiple icon buttons. Views are not needed nor used for individual buttons. + * menu with multiple icon buttons + * views are not needed nor used for individual buttons */ -var IconButtonMenuView = Backbone.View.extend({ +var IconButtonMenuView = Backbone.View.extend( +{ + // tag tagName: 'div', - initialize: function() { + // initialize + initialize: function() + { this.render(); }, - render: function() { + // render + render: function() + { + // initialize icon buttons var self = this; - this.collection.each(function(button) { - // Create and add icon button to menu. + this.collection.each(function(button) + { + // create and add icon button to menu var elt = $('').attr('href', 'javascript:void(0)') .attr('title', button.attributes.title) @@ -104,17 +175,17 @@ var IconButtonMenuView = Backbone.View.extend({ .appendTo(self.$el) .click(button.attributes.on_click); - if (button.attributes.tooltip_config) { + // configure tooltip + if (button.attributes.tooltip_config) elt.tooltip(button.attributes.tooltip_config); - } - // If there are options, add popup menu to icon. + // add popup menu to icon var menu_options = button.get('options'); - if (menu_options) { + if (menu_options) make_popupmenu(elt, menu_options); - } - }); + + // return return this; } }); @@ -125,20 +196,23 @@ var IconButtonMenuView = Backbone.View.extend({ * defines an icon button. Each dictionary must have the following * elements: icon_class, title, and on_click. */ -var create_icon_buttons_menu = function(config, global_config) { - if (!global_config) { global_config = {}; } +var create_icon_buttons_menu = function(config, global_config) +{ + // initialize global configuration + if (!global_config) global_config = {}; - // Create and initialize menu. + // create and initialize menu var buttons = new IconButtonCollection( - _.map(config, function(button_config) { - return new IconButton(_.extend(button_config, global_config)); - }) - ); + _.map(config, function(button_config) + { + return new IconButton(_.extend(button_config, global_config)); + }) + ); + // return menu return new IconButtonMenuView( {collection: buttons} ); }; - // ============================================================================= /** * @@ -155,196 +229,232 @@ var GridView = Backbone.View.extend({ }); // ============================================================================= -/** - * Necessary Galaxy paths. - */ -var GalaxyPaths = Backbone.Model.extend({ - defaults: { - root_path: "", - image_path: "" - } -}); - - -// ============================================================================= -/** @class View for a popup menu - * @name PopupMenu - * - * @constructs +/** + * view for a popup menu */ var PopupMenu = Backbone.View.extend( -/** @lends PopupMenu.prototype */{ - +{ /* TODO: add submenus add hrefs test various html keys add make_popupmenus style - get template inside this file somehow */ /** Cache the desired button element and options, set up the button click handler * NOTE: attaches this view as HTML/jQ data on the button for later use. */ //TODO: include docs on special option keys (divider, checked, etc.) - initialize : function( $button, options ){ + initialize: function($button, options) + { // default settings - this.$button = $button || $( '
' ); + this.$button = $button || $(''); this.options = options || []; // set up button click -> open menu behavior var menu = this; - this.$button.click( function( event ){ - menu._renderAndShow( event ); - //event.stopPropagation(); + this.$button.click(function(event) + { + menu._renderAndShow(event); return false; }); // attach this view as a data object on the button - for later access //TODO:?? memleak? - this.$button.data( 'PopupMenu', this ); - - // template loading is problematic - ui is loaded in base.mako - // and the template (prev.) needed to be loaded before ui + this.$button.data('PopupMenu', this); }, - /** Render the menu. NOTE: doesn't attach itself to the DOM. - * @see PopupMenu#_renderAndShow - */ - render : function(){ + // render the menu + // this menu doesn't attach itself to the DOM (see _renderAndShow) + render: function() + { + // link this popup var menu = this; // render the menu body - this.$el.addClass( 'popmenu-wrapper' ) - .css({ - position: 'absolute', - display: 'none' + this.$el.addClass('popmenu-wrapper') + .css( + { + position : 'absolute', + display : 'none' }); - //BUG: anchors within a.popupmenu-option render OUTSIDE the a.popupmenu-option!? - this.$el.html( PopupMenu.templates.menu({ - options : this.options, - // sets menu div id to '{{ id }}-menu' - id : this.$button.attr( 'id' ) - })); + // use template + this.$el.html(this.template(this.$button.attr('id'), this.options)); // set up behavior on each link/anchor elem - if( this.options.length ){ - this.$el.find( 'li' ).each( function( i, li ){ - var $li = $( li ), + if(this.options.length) + { + this.$el.find('li').each(function(i, li) + { + var $li = $(li), $anchor = $li.children( 'a.popupmenu-option' ), - menuFunc = menu.options[ i ].func; + menuFunc = menu.options[i].func; - if( $anchor.length && menuFunc ){ - $anchor.click( function( event ){ - menuFunc( event, menu.options[ i ] ); + // click event + if($anchor.length && menuFunc) + { + $anchor.click(function(event) + { + menuFunc(event, menu.options[i]); }); } // cache the anchor as a jq obj within the options obj - menu.options[ i ].$li = $li; + menu.options[i].$li = $li; }); } return this; }, - /** Get the absolute position/offset for the menu - */ - _getShownPosition : function( clickEvent ){ - var menuWidth = this.$el.width(), - // display menu horiz. centered on click... - x = clickEvent.pageX - menuWidth / 2 ; + // get the absolute position/offset for the menu + _getShownPosition : function( clickEvent ) + { + // get element width + var menuWidth = this.$el.width(); + + // display menu horiz. centered on click... + var x = clickEvent.pageX - menuWidth / 2 ; - // ...but adjust that to handle horiz. scroll and window dimensions (draw entirely on visible screen area) + // adjust to handle horiz. scroll and window dimensions (draw entirely on visible screen area) x = Math.min( x, $( document ).scrollLeft() + $( window ).width() - menuWidth - 5 ); x = Math.max( x, $( document ).scrollLeft() + 5 ); + // return return { top: clickEvent.pageY, left: x }; }, - /** Render the menu, append to the page body at the click position, and set up the 'click-away' handlers, show - */ - _renderAndShow : function( clickEvent ){ + // render the menu, append to the page body at the click position, and set up the 'click-away' handlers, show + _renderAndShow: function(clickEvent) + { this.render(); - this.$el.appendTo( 'body' ); - this.$el.css( this._getShownPosition( clickEvent ) ); + this.$el.appendTo('body'); + this.$el.css( this._getShownPosition(clickEvent)); this._setUpCloseBehavior(); this.$el.show(); }, - /** Bind an event handler to all available frames so that when anything is clicked - * * the menu is removed from the DOM - * * The event handler unbinds itself - */ - _setUpCloseBehavior : function(){ - var menu = this, - // function to close popup and unbind itself - closePopupWhenClicked = function( $elClicked ){ - $elClicked.bind( "click.close_popup", function(){ - menu.remove(); - $elClicked.unbind( "click.close_popup" ); - }); - }; + // bind an event handler to all available frames so that when anything is clicked + // the menu is removed from the DOM and the event handler unbinds itself + _setUpCloseBehavior: function() + { + // function to close popup and unbind itself + var menu = this; + var closePopupWhenClicked = function($elClicked) + { + $elClicked.bind("click.close_popup", function() + { + menu.remove(); + $elClicked.unbind("click.close_popup"); + }); + }; // bind to current, parent, and sibling frames - //TODO: (Assuming for now that this is the best way to do this...) - closePopupWhenClicked( $( window.document ) ); - closePopupWhenClicked( $( window.top.document ) ); - _.each( window.top.frames, function( siblingFrame ){ - closePopupWhenClicked( $( siblingFrame.document ) ); + closePopupWhenClicked($(window.document)); + closePopupWhenClicked($(window.top.document)); + _.each(window.top.frames, function(siblingFrame) + { + closePopupWhenClicked($(siblingFrame.document)); }); }, - /** Add a menu option/item at the given index - */ - addItem : function( item, index ){ + // add a menu option/item at the given index + addItem: function(item, index) + { // append to end if no index - index = ( index >= 0 )?( index ):( this.options.length ); - this.options.splice( index, 0, item ); + index = (index >= 0) ? index : this.options.length; + this.options.splice(index, 0, item); return this; }, - /** Remove a menu option/item at the given index - */ - removeItem : function( index ){ - if( index >=0 ){ - this.options.splice( index, 1 ); - } + // remove a menu option/item at the given index + removeItem: function(index) + { + if(index >=0) + this.options.splice(index, 1); return this; }, - /** Search for a menu option by it's html - */ - findIndexByHtml : function( html ){ - for( var i=0; i