mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
581 lines
18 KiB
JavaScript
581 lines
18 KiB
JavaScript
/**
|
|
* functions for creating major ui elements
|
|
*/
|
|
|
|
/**
|
|
* backbone model for icon buttons
|
|
*/
|
|
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);
|
|
},
|
|
|
|
// render
|
|
render: function()
|
|
{
|
|
// hide tooltip
|
|
this.$el.tooltip('hide');
|
|
|
|
// create element
|
|
var new_elem = this.template(this.model.attributes);
|
|
|
|
// 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:
|
|
{
|
|
'click' : 'click'
|
|
},
|
|
|
|
// click
|
|
click: function( event )
|
|
{
|
|
// if on_click pass to that function
|
|
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 = '<a ' + buffer + '/>';
|
|
else
|
|
buffer = '<span ' + buffer + '/>';
|
|
|
|
// return element
|
|
return $(buffer);
|
|
}
|
|
});
|
|
|
|
// define collection
|
|
var IconButtonCollection = Backbone.Collection.extend(
|
|
{
|
|
model: IconButton
|
|
});
|
|
|
|
/**
|
|
* menu with multiple icon buttons
|
|
* views are not needed nor used for individual buttons
|
|
*/
|
|
var IconButtonMenuView = Backbone.View.extend(
|
|
{
|
|
// tag
|
|
tagName: 'div',
|
|
|
|
// initialize
|
|
initialize: function()
|
|
{
|
|
this.render();
|
|
},
|
|
|
|
// render
|
|
render: function()
|
|
{
|
|
// initialize icon buttons
|
|
var self = this;
|
|
this.collection.each(function(button)
|
|
{
|
|
// create and add icon button to menu
|
|
var elt =
|
|
$('<a/>').attr('href', 'javascript:void(0)')
|
|
.attr('title', button.attributes.title)
|
|
.addClass('icon-button menu-button')
|
|
.addClass(button.attributes.icon_class)
|
|
.appendTo(self.$el)
|
|
.click(button.attributes.on_click);
|
|
|
|
// configure tooltip
|
|
if (button.attributes.tooltip_config)
|
|
elt.tooltip(button.attributes.tooltip_config);
|
|
|
|
// add popup menu to icon
|
|
var menu_options = button.get('options');
|
|
if (menu_options)
|
|
make_popupmenu(elt, menu_options);
|
|
});
|
|
|
|
// return
|
|
return this;
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Returns an IconButtonMenuView for the provided configuration.
|
|
* Configuration is a list of dictionaries where each dictionary
|
|
* 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)
|
|
{
|
|
// initialize global configuration
|
|
if (!global_config) global_config = {};
|
|
|
|
// create and initialize menu
|
|
var buttons = new IconButtonCollection(
|
|
_.map(config, function(button_config)
|
|
{
|
|
return new IconButton(_.extend(button_config, global_config));
|
|
})
|
|
);
|
|
|
|
// return menu
|
|
return new IconButtonMenuView( {collection: buttons} );
|
|
};
|
|
|
|
// =============================================================================
|
|
/**
|
|
*
|
|
*/
|
|
var Grid = Backbone.Collection.extend({
|
|
|
|
});
|
|
|
|
/**
|
|
*
|
|
*/
|
|
var GridView = Backbone.View.extend({
|
|
|
|
});
|
|
|
|
// =============================================================================
|
|
/**
|
|
* view for a popup menu
|
|
*/
|
|
var PopupMenu = Backbone.View.extend(
|
|
{
|
|
/* TODO:
|
|
add submenus
|
|
add hrefs
|
|
test various html keys
|
|
add make_popupmenus style
|
|
*/
|
|
/** 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)
|
|
{
|
|
// default settings
|
|
this.$button = $button || $('<div/>');
|
|
this.options = options || [];
|
|
|
|
// set up button click -> open menu behavior
|
|
var menu = this;
|
|
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);
|
|
},
|
|
|
|
// 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'
|
|
});
|
|
|
|
// 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),
|
|
$anchor = $li.children( 'a.popupmenu-option' ),
|
|
menuFunc = menu.options[i].func;
|
|
|
|
// 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;
|
|
});
|
|
}
|
|
return this;
|
|
},
|
|
|
|
// 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 ;
|
|
|
|
// 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)
|
|
{
|
|
this.render();
|
|
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 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
|
|
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)
|
|
{
|
|
// append to end if no index
|
|
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);
|
|
return this;
|
|
},
|
|
|
|
// search for a menu option by it's html
|
|
findIndexByHtml: function(html)
|
|
{
|
|
for(var i = 0; i < this.options.length; i++)
|
|
if(_.has(this.options[i], 'html') && (this.options[i].html === html))
|
|
return i;
|
|
return null;
|
|
},
|
|
|
|
// search for a menu option by it's html
|
|
findItemByHtml: function(html)
|
|
{
|
|
return this.options[(this.findIndexByHtml(html))];
|
|
},
|
|
|
|
// string representation
|
|
toString: function()
|
|
{
|
|
return 'PopupMenu';
|
|
},
|
|
|
|
// template
|
|
template: function(id, options)
|
|
{
|
|
// initialize template
|
|
var tmpl = '<ul id="' + id + '-menu" class="dropdown-menu">';
|
|
|
|
// check item number
|
|
if (options.length > 0)
|
|
{
|
|
// add option
|
|
for (var i in options)
|
|
{
|
|
// get item
|
|
var item = options[i];
|
|
|
|
// check for divider
|
|
if (item.divider)
|
|
{
|
|
// add divider
|
|
tmpl += '<li class="divider"></li>';
|
|
} else {
|
|
// identify header
|
|
if(item.header)
|
|
{
|
|
tmpl += '<li class="head"><a href="javascript:void(0);">' + item.html + '</a></li>';
|
|
} else {
|
|
// add href
|
|
if (item.href)
|
|
{
|
|
tmpl += '<li><a href="' + item.href + '"';
|
|
tmpl += 'target="' + item.target + '"';
|
|
} else
|
|
tmpl += '<li><a href="javascript:void(0);"';
|
|
|
|
// add class
|
|
tmpl += 'class="popupmenu-option">'
|
|
|
|
// add target
|
|
if (item.checked)
|
|
tmpl += '<span class="fa-icon-ok"></span>';
|
|
|
|
// add html
|
|
tmpl += item.html;
|
|
}
|
|
}
|
|
}
|
|
} else
|
|
tmpl += '<li>No Options.</li>';
|
|
|
|
// return
|
|
return tmpl + '</ul>';
|
|
}
|
|
});
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// the following class functions are bridges from the original make_popupmenu and make_popup_menus
|
|
// to the newer backbone.js PopupMenu
|
|
|
|
/** Create a PopupMenu from simple map initial_options activated by clicking button_element.
|
|
* Converts initial_options to object array used by PopupMenu.
|
|
* @param {jQuery|DOMElement} button_element element which, when clicked, activates menu
|
|
* @param {Object} initial_options map of key -> values, where
|
|
* key is option text, value is fn to call when option is clicked
|
|
* @returns {PopupMenu} the PopupMenu created
|
|
*/
|
|
PopupMenu.make_popupmenu = function( button_element, initial_options ){
|
|
var convertedOptions = [];
|
|
_.each( initial_options, function( optionVal, optionKey ){
|
|
var newOption = { html: optionKey };
|
|
|
|
// keys with null values indicate: header
|
|
if( optionVal === null ){ // !optionVal? (null only?)
|
|
newOption.header = true;
|
|
|
|
// keys with function values indicate: a menu option
|
|
} else if( jQuery.type( optionVal ) === 'function' ){
|
|
newOption.func = optionVal;
|
|
}
|
|
//TODO:?? any other special optionVals?
|
|
// there was no divider option originally
|
|
convertedOptions.push( newOption );
|
|
});
|
|
return new PopupMenu( $( button_element ), convertedOptions );
|
|
};
|
|
|
|
/** Find all anchors in $parent (using selector) and covert anchors into a PopupMenu options map.
|
|
* @param {jQuery} $parent the element that contains the links to convert to options
|
|
* @param {String} selector jq selector string to find links
|
|
* @returns {Object[]} the options array to initialize a PopupMenu
|
|
*/
|
|
//TODO: lose parent and selector, pass in array of links, use map to return options
|
|
PopupMenu.convertLinksToOptions = function( $parent, selector )
|
|
{
|
|
$parent = $($parent);
|
|
selector = selector || 'a';
|
|
var options = [];
|
|
$parent.find( selector ).each( function( elem, i )
|
|
{
|
|
var option = {}, $link = $( elem );
|
|
|
|
// convert link text to the option text (html) and the href into the option func
|
|
option.html = $link.text();
|
|
if( linkHref )
|
|
{
|
|
var linkHref = $link.attr( 'href' ),
|
|
linkTarget = $link.attr( 'target' ),
|
|
confirmText = $link.attr( 'confirm' );
|
|
|
|
option.func = function()
|
|
{
|
|
// if there's a "confirm" attribute, throw up a confirmation dialog, and
|
|
// if the user cancels - do nothing
|
|
if( ( confirmText ) && ( !confirm( confirmText ) ) ){ return; }
|
|
|
|
// if there's no confirm attribute, or the user accepted the confirm dialog:
|
|
var f;
|
|
switch( linkTarget )
|
|
{
|
|
// relocate the center panel
|
|
case '_parent':
|
|
window.parent.location = linkHref;
|
|
break;
|
|
|
|
// relocate the entire window
|
|
case '_top':
|
|
window.top.location = linkHref;
|
|
break;
|
|
|
|
// Http request target is a window named demolocal on the local box
|
|
//TODO: I still don't understand this option (where the hell does f get set? confirm?)
|
|
case 'demo':
|
|
if( f === undefined || f.closed ){
|
|
f = window.open( linkHref, linkTarget );
|
|
f.creator = self;
|
|
}
|
|
break;
|
|
|
|
// relocate this panel
|
|
default:
|
|
window.location = linkHref;
|
|
}
|
|
};
|
|
}
|
|
options.push( option );
|
|
});
|
|
return options;
|
|
};
|
|
|
|
/** Create a single popupmenu from existing DOM button and anchor elements
|
|
* @param {jQuery} $buttonElement the element that when clicked will open the menu
|
|
* @param {jQuery} $menuElement the element that contains the anchors to convert into a menu
|
|
* @param {String} menuElementLinkSelector jq selector string used to find anchors to be made into menu options
|
|
* @returns {PopupMenu} the PopupMenu (Backbone View) that can render, control the menu
|
|
*/
|
|
PopupMenu.fromExistingDom = function( $buttonElement, $menuElement, menuElementLinkSelector ){
|
|
$buttonElement = $( $buttonElement );
|
|
$menuElement = $( $menuElement );
|
|
var options = PopupMenu.convertLinksToOptions( $menuElement, menuElementLinkSelector );
|
|
// we're done with the menu (having converted it to an options map)
|
|
$menuElement.remove();
|
|
return new PopupMenu( $buttonElement, options );
|
|
};
|
|
|
|
/** Create all popupmenus within a document or a more specific element
|
|
* @param {DOMElement} parent the DOM element in which to search for popupmenus to build (defaults to document)
|
|
* @param {String} menuSelector jq selector string to find popupmenu menu elements (defaults to "div[popupmenu]")
|
|
* @param {Function} buttonSelectorBuildFn the function to build the jq button selector.
|
|
* Will be passed $menuElement, parent.
|
|
* (Defaults to return '#' + $menuElement.attr( 'popupmenu' ); )
|
|
* @returns {PopupMenu[]} array of popupmenus created
|
|
*/
|
|
PopupMenu.make_popup_menus = function( parent, menuSelector, buttonSelectorBuildFn ){
|
|
parent = parent || document;
|
|
// orig. Glx popupmenu menus have a (non-std) attribute 'popupmenu'
|
|
// which contains the id of the button that activates the menu
|
|
menuSelector = menuSelector || 'div[popupmenu]';
|
|
// default to (orig. Glx) matching button to menu by using the popupmenu attr of the menu as the id of the button
|
|
buttonSelectorBuildFn = buttonSelectorBuildFn || function( $menuElement, parent ){
|
|
return '#' + $menuElement.attr( 'popupmenu' );
|
|
};
|
|
|
|
// aggregate and return all PopupMenus
|
|
var popupMenusCreated = [];
|
|
$( parent ).find( menuSelector ).each( function(){
|
|
var $menuElement = $( this ),
|
|
$buttonElement = $( parent ).find( buttonSelectorBuildFn( $menuElement, parent ) );
|
|
popupMenusCreated.push( PopupMenu.fromDom( $buttonElement, $menuElement ) );
|
|
$buttonElement.addClass( 'popup' );
|
|
});
|
|
return popupMenusCreated;
|
|
}
|
|
|