PageRenderTime 22ms CodeModel.GetById 1ms RepoModel.GetById 1ms app.codeStats 0ms

/static/scripts/mvc/ui.js

https://bitbucket.org/chapmanb/galaxy-central/
JavaScript | 592 lines | 353 code | 75 blank | 164 comment | 37 complexity | a99f4d5e25ac810a2576151bc7392e8a MD5 | raw file
  1. /**
  2. * necessary galaxy paths
  3. */
  4. var GalaxyPaths = Backbone.Model.extend(
  5. {
  6. defaults:
  7. {
  8. root_path: "",
  9. image_path: ""
  10. }
  11. });
  12. /**
  13. * functions for creating large ui elements
  14. */
  15. /**
  16. * backbone model for icon buttons
  17. */
  18. var IconButton = Backbone.Model.extend(
  19. {
  20. defaults:
  21. {
  22. title : "",
  23. icon_class : "",
  24. on_click : null,
  25. menu_options : null,
  26. is_menu_button : true,
  27. id : null,
  28. href : null,
  29. target : null,
  30. enabled : true,
  31. visible : true,
  32. tooltip_config : {}
  33. }
  34. });
  35. /**
  36. * backbone view for icon buttons
  37. */
  38. var IconButtonView = Backbone.View.extend(
  39. {
  40. // initialize
  41. initialize: function()
  42. {
  43. // better rendering this way
  44. this.model.attributes.tooltip_config = { placement : 'bottom' };
  45. this.model.bind('change', this.render, this);
  46. },
  47. // render
  48. render: function()
  49. {
  50. // hide tooltip
  51. this.$el.tooltip('hide');
  52. // create element
  53. var new_elem = this.template(this.model.attributes);
  54. // configure tooltip
  55. new_elem.tooltip(this.model.get('tooltip_config'));
  56. // replace
  57. this.$el.replaceWith(new_elem);
  58. this.setElement(new_elem);
  59. // return
  60. return this;
  61. },
  62. // events
  63. events:
  64. {
  65. 'click' : 'click'
  66. },
  67. // click
  68. click: function( event )
  69. {
  70. // if on_click pass to that function
  71. if(this.model.attributes.on_click)
  72. {
  73. this.model.attributes.on_click(event);
  74. return false;
  75. }
  76. // otherwise, bubble up (to href or whatever)
  77. return true;
  78. },
  79. // generate html element
  80. template: function(options)
  81. {
  82. // initialize
  83. var buffer = 'title="' + options.title + '" class="icon-button';
  84. // is menu button
  85. if(options.is_menu_button)
  86. buffer += ' menu-button';
  87. // define tooltip
  88. if(options.title)
  89. buffer += ' tooltip';
  90. // add icon class
  91. buffer += ' ' + options.icon_class;
  92. // add enabled/disabled class
  93. if(!options.enabled)
  94. buffer += '_disabled';
  95. // close class tag
  96. buffer += '"';
  97. // add id
  98. if(options.id)
  99. buffer += ' id="' + options.id + '"';
  100. // add href
  101. buffer += ' href="' + options.href + '"';
  102. // add target for href
  103. if(options.target)
  104. buffer += ' target="' + options.target + '"';
  105. // set visibility
  106. if(!options.visible)
  107. buffer += ' style="display: none;"';
  108. // enabled/disabled
  109. if (options.enabled)
  110. buffer = '<a ' + buffer + '/>';
  111. else
  112. buffer = '<span ' + buffer + '/>';
  113. // return element
  114. return $(buffer);
  115. }
  116. });
  117. // define collection
  118. var IconButtonCollection = Backbone.Collection.extend(
  119. {
  120. model: IconButton
  121. });
  122. /**
  123. * menu with multiple icon buttons
  124. * views are not needed nor used for individual buttons
  125. */
  126. var IconButtonMenuView = Backbone.View.extend(
  127. {
  128. // tag
  129. tagName: 'div',
  130. // initialize
  131. initialize: function()
  132. {
  133. this.render();
  134. },
  135. // render
  136. render: function()
  137. {
  138. // initialize icon buttons
  139. var self = this;
  140. this.collection.each(function(button)
  141. {
  142. // create and add icon button to menu
  143. var elt =
  144. $('<a/>').attr('href', 'javascript:void(0)')
  145. .attr('title', button.attributes.title)
  146. .addClass('icon-button menu-button')
  147. .addClass(button.attributes.icon_class)
  148. .appendTo(self.$el)
  149. .click(button.attributes.on_click);
  150. // configure tooltip
  151. if (button.attributes.tooltip_config)
  152. elt.tooltip(button.attributes.tooltip_config);
  153. // add popup menu to icon
  154. var menu_options = button.get('options');
  155. if (menu_options)
  156. make_popupmenu(elt, menu_options);
  157. });
  158. // return
  159. return this;
  160. }
  161. });
  162. /**
  163. * Returns an IconButtonMenuView for the provided configuration.
  164. * Configuration is a list of dictionaries where each dictionary
  165. * defines an icon button. Each dictionary must have the following
  166. * elements: icon_class, title, and on_click.
  167. */
  168. var create_icon_buttons_menu = function(config, global_config)
  169. {
  170. // initialize global configuration
  171. if (!global_config) global_config = {};
  172. // create and initialize menu
  173. var buttons = new IconButtonCollection(
  174. _.map(config, function(button_config)
  175. {
  176. return new IconButton(_.extend(button_config, global_config));
  177. })
  178. );
  179. // return menu
  180. return new IconButtonMenuView( {collection: buttons} );
  181. };
  182. // =============================================================================
  183. /**
  184. *
  185. */
  186. var Grid = Backbone.Collection.extend({
  187. });
  188. /**
  189. *
  190. */
  191. var GridView = Backbone.View.extend({
  192. });
  193. // =============================================================================
  194. /**
  195. * view for a popup menu
  196. */
  197. var PopupMenu = Backbone.View.extend(
  198. {
  199. /* TODO:
  200. add submenus
  201. add hrefs
  202. test various html keys
  203. add make_popupmenus style
  204. */
  205. /** Cache the desired button element and options, set up the button click handler
  206. * NOTE: attaches this view as HTML/jQ data on the button for later use.
  207. */
  208. //TODO: include docs on special option keys (divider, checked, etc.)
  209. initialize: function($button, options)
  210. {
  211. // default settings
  212. this.$button = $button || $('<div/>');
  213. this.options = options || [];
  214. // set up button click -> open menu behavior
  215. var menu = this;
  216. this.$button.click(function(event)
  217. {
  218. menu._renderAndShow(event);
  219. return false;
  220. });
  221. // attach this view as a data object on the button - for later access
  222. //TODO:?? memleak?
  223. this.$button.data('PopupMenu', this);
  224. },
  225. // render the menu
  226. // this menu doesn't attach itself to the DOM (see _renderAndShow)
  227. render: function()
  228. {
  229. // link this popup
  230. var menu = this;
  231. // render the menu body
  232. this.$el.addClass('popmenu-wrapper')
  233. .css(
  234. {
  235. position : 'absolute',
  236. display : 'none'
  237. });
  238. // use template
  239. this.$el.html(this.template(this.$button.attr('id'), this.options));
  240. // set up behavior on each link/anchor elem
  241. if(this.options.length)
  242. {
  243. this.$el.find('li').each(function(i, li)
  244. {
  245. var $li = $(li),
  246. $anchor = $li.children( 'a.popupmenu-option' ),
  247. menuFunc = menu.options[i].func;
  248. // click event
  249. if($anchor.length && menuFunc)
  250. {
  251. $anchor.click(function(event)
  252. {
  253. menuFunc(event, menu.options[i]);
  254. });
  255. }
  256. // cache the anchor as a jq obj within the options obj
  257. menu.options[i].$li = $li;
  258. });
  259. }
  260. return this;
  261. },
  262. // get the absolute position/offset for the menu
  263. _getShownPosition : function( clickEvent )
  264. {
  265. // get element width
  266. var menuWidth = this.$el.width();
  267. // display menu horiz. centered on click...
  268. var x = clickEvent.pageX - menuWidth / 2 ;
  269. // adjust to handle horiz. scroll and window dimensions (draw entirely on visible screen area)
  270. x = Math.min( x, $( document ).scrollLeft() + $( window ).width() - menuWidth - 5 );
  271. x = Math.max( x, $( document ).scrollLeft() + 5 );
  272. // return
  273. return {
  274. top: clickEvent.pageY,
  275. left: x
  276. };
  277. },
  278. // render the menu, append to the page body at the click position, and set up the 'click-away' handlers, show
  279. _renderAndShow: function(clickEvent)
  280. {
  281. this.render();
  282. this.$el.appendTo('body');
  283. this.$el.css( this._getShownPosition(clickEvent));
  284. this._setUpCloseBehavior();
  285. this.$el.show();
  286. },
  287. // bind an event handler to all available frames so that when anything is clicked
  288. // the menu is removed from the DOM and the event handler unbinds itself
  289. _setUpCloseBehavior: function()
  290. {
  291. // function to close popup and unbind itself
  292. var menu = this;
  293. var closePopupWhenClicked = function($elClicked)
  294. {
  295. $elClicked.bind("click.close_popup", function()
  296. {
  297. menu.remove();
  298. $elClicked.unbind("click.close_popup");
  299. });
  300. };
  301. // bind to current, parent, and sibling frames
  302. closePopupWhenClicked($(window.document));
  303. closePopupWhenClicked($(window.top.document));
  304. _.each(window.top.frames, function(siblingFrame)
  305. {
  306. closePopupWhenClicked($(siblingFrame.document));
  307. });
  308. },
  309. // add a menu option/item at the given index
  310. addItem: function(item, index)
  311. {
  312. // append to end if no index
  313. index = (index >= 0) ? index : this.options.length;
  314. this.options.splice(index, 0, item);
  315. return this;
  316. },
  317. // remove a menu option/item at the given index
  318. removeItem: function(index)
  319. {
  320. if(index >=0)
  321. this.options.splice(index, 1);
  322. return this;
  323. },
  324. // search for a menu option by it's html
  325. findIndexByHtml: function(html)
  326. {
  327. for(var i = 0; i < this.options.length; i++)
  328. if(_.has(this.options[i], 'html') && (this.options[i].html === html))
  329. return i;
  330. return null;
  331. },
  332. // search for a menu option by it's html
  333. findItemByHtml: function(html)
  334. {
  335. return this.options[(this.findIndexByHtml(html))];
  336. },
  337. // string representation
  338. toString: function()
  339. {
  340. return 'PopupMenu';
  341. },
  342. // template
  343. template: function(id, options)
  344. {
  345. // initialize template
  346. var tmpl = '<ul id="' + id + '-menu" class="dropdown-menu">';
  347. // check item number
  348. if (options.length > 0)
  349. {
  350. // add option
  351. for (var i in options)
  352. {
  353. // get item
  354. var item = options[i];
  355. // check for divider
  356. if (item.divider)
  357. {
  358. // add divider
  359. tmpl += '<li class="divider"></li>';
  360. } else {
  361. // identify header
  362. if(item.header)
  363. {
  364. tmpl += '<li class="head"><a href="javascript:void(0);">' + item.html + '</a></li>';
  365. } else {
  366. // add href
  367. if (item.href)
  368. {
  369. tmpl += '<li><a href="' + item.href + '"';
  370. tmpl += 'target="' + item.target + '"';
  371. } else
  372. tmpl += '<li><a href="javascript:void(0);"';
  373. // add class
  374. tmpl += 'class="popupmenu-option">'
  375. // add target
  376. if (item.checked)
  377. tmpl += '<span class="fa-icon-ok"></span>';
  378. // add html
  379. tmpl += item.html;
  380. }
  381. }
  382. }
  383. } else
  384. tmpl += '<li>No Options.</li>';
  385. // return
  386. return tmpl + '</ul>';
  387. }
  388. });
  389. // -----------------------------------------------------------------------------
  390. // the following class functions are bridges from the original make_popupmenu and make_popup_menus
  391. // to the newer backbone.js PopupMenu
  392. /** Create a PopupMenu from simple map initial_options activated by clicking button_element.
  393. * Converts initial_options to object array used by PopupMenu.
  394. * @param {jQuery|DOMElement} button_element element which, when clicked, activates menu
  395. * @param {Object} initial_options map of key -> values, where
  396. * key is option text, value is fn to call when option is clicked
  397. * @returns {PopupMenu} the PopupMenu created
  398. */
  399. PopupMenu.make_popupmenu = function( button_element, initial_options ){
  400. var convertedOptions = [];
  401. _.each( initial_options, function( optionVal, optionKey ){
  402. var newOption = { html: optionKey };
  403. // keys with null values indicate: header
  404. if( optionVal === null ){ // !optionVal? (null only?)
  405. newOption.header = true;
  406. // keys with function values indicate: a menu option
  407. } else if( jQuery.type( optionVal ) === 'function' ){
  408. newOption.func = optionVal;
  409. }
  410. //TODO:?? any other special optionVals?
  411. // there was no divider option originally
  412. convertedOptions.push( newOption );
  413. });
  414. return new PopupMenu( $( button_element ), convertedOptions );
  415. };
  416. /** Find all anchors in $parent (using selector) and covert anchors into a PopupMenu options map.
  417. * @param {jQuery} $parent the element that contains the links to convert to options
  418. * @param {String} selector jq selector string to find links
  419. * @returns {Object[]} the options array to initialize a PopupMenu
  420. */
  421. //TODO: lose parent and selector, pass in array of links, use map to return options
  422. PopupMenu.convertLinksToOptions = function( $parent, selector )
  423. {
  424. $parent = $($parent);
  425. selector = selector || 'a';
  426. var options = [];
  427. $parent.find( selector ).each( function( elem, i )
  428. {
  429. var option = {}, $link = $( elem );
  430. // convert link text to the option text (html) and the href into the option func
  431. option.html = $link.text();
  432. if( linkHref )
  433. {
  434. var linkHref = $link.attr( 'href' ),
  435. linkTarget = $link.attr( 'target' ),
  436. confirmText = $link.attr( 'confirm' );
  437. option.func = function()
  438. {
  439. // if there's a "confirm" attribute, throw up a confirmation dialog, and
  440. // if the user cancels - do nothing
  441. if( ( confirmText ) && ( !confirm( confirmText ) ) ){ return; }
  442. // if there's no confirm attribute, or the user accepted the confirm dialog:
  443. var f;
  444. switch( linkTarget )
  445. {
  446. // relocate the center panel
  447. case '_parent':
  448. window.parent.location = linkHref;
  449. break;
  450. // relocate the entire window
  451. case '_top':
  452. window.top.location = linkHref;
  453. break;
  454. // Http request target is a window named demolocal on the local box
  455. //TODO: I still don't understand this option (where the hell does f get set? confirm?)
  456. case 'demo':
  457. if( f === undefined || f.closed ){
  458. f = window.open( linkHref, linkTarget );
  459. f.creator = self;
  460. }
  461. break;
  462. // relocate this panel
  463. default:
  464. window.location = linkHref;
  465. }
  466. };
  467. }
  468. options.push( option );
  469. });
  470. return options;
  471. };
  472. /** Create a single popupmenu from existing DOM button and anchor elements
  473. * @param {jQuery} $buttonElement the element that when clicked will open the menu
  474. * @param {jQuery} $menuElement the element that contains the anchors to convert into a menu
  475. * @param {String} menuElementLinkSelector jq selector string used to find anchors to be made into menu options
  476. * @returns {PopupMenu} the PopupMenu (Backbone View) that can render, control the menu
  477. */
  478. PopupMenu.fromExistingDom = function( $buttonElement, $menuElement, menuElementLinkSelector ){
  479. $buttonElement = $( $buttonElement );
  480. $menuElement = $( $menuElement );
  481. var options = PopupMenu.convertLinksToOptions( $menuElement, menuElementLinkSelector );
  482. // we're done with the menu (having converted it to an options map)
  483. $menuElement.remove();
  484. return new PopupMenu( $buttonElement, options );
  485. };
  486. /** Create all popupmenus within a document or a more specific element
  487. * @param {DOMElement} parent the DOM element in which to search for popupmenus to build (defaults to document)
  488. * @param {String} menuSelector jq selector string to find popupmenu menu elements (defaults to "div[popupmenu]")
  489. * @param {Function} buttonSelectorBuildFn the function to build the jq button selector.
  490. * Will be passed $menuElement, parent.
  491. * (Defaults to return '#' + $menuElement.attr( 'popupmenu' ); )
  492. * @returns {PopupMenu[]} array of popupmenus created
  493. */
  494. PopupMenu.make_popup_menus = function( parent, menuSelector, buttonSelectorBuildFn ){
  495. parent = parent || document;
  496. // orig. Glx popupmenu menus have a (non-std) attribute 'popupmenu'
  497. // which contains the id of the button that activates the menu
  498. menuSelector = menuSelector || 'div[popupmenu]';
  499. // default to (orig. Glx) matching button to menu by using the popupmenu attr of the menu as the id of the button
  500. buttonSelectorBuildFn = buttonSelectorBuildFn || function( $menuElement, parent ){
  501. return '#' + $menuElement.attr( 'popupmenu' );
  502. };
  503. // aggregate and return all PopupMenus
  504. var popupMenusCreated = [];
  505. $( parent ).find( menuSelector ).each( function(){
  506. var $menuElement = $( this ),
  507. $buttonElement = $( parent ).find( buttonSelectorBuildFn( $menuElement, parent ) );
  508. popupMenusCreated.push( PopupMenu.fromDom( $buttonElement, $menuElement ) );
  509. $buttonElement.addClass( 'popup' );
  510. });
  511. return popupMenusCreated;
  512. }