diff --git a/client/galaxy/scripts/jq-plugins/ui/pagination.js b/client/galaxy/scripts/jq-plugins/ui/pagination.js
new file mode 100644
index 00000000000..a0f13183703
--- /dev/null
+++ b/client/galaxy/scripts/jq-plugins/ui/pagination.js
@@ -0,0 +1,226 @@
+// from: https://raw.githubusercontent.com/umdjs/umd/master/jqueryPlugin.js
+// Uses AMD or browser globals to create a jQuery plugin.
+(function (factory) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module.
+ define(['jquery'], factory);
+ } else {
+ // Browser globals
+ factory(jQuery);
+ }
+
+}(function ($) {
+
+ /** Builds (twitter bootstrap styled) pagination controls.
+ * If the totalDataSize is not null, a horizontal list of page buttons is displayed.
+ * If totalDataSize is null, two links ('Prev' and 'Next) are displayed.
+ * When pages are changed, a 'pagination.page-change' event is fired
+ * sending the event and the (0-based) page requested.
+ */
+ function Pagination( element, options ){
+ /** the total number of pages */
+ this.numPages = null;
+ /** the current, active page */
+ this.currPage = 0;
+ return this.init( element, options );
+ }
+
+ /** data key under which this object will be stored in the element */
+ Pagination.prototype.DATA_KEY = 'pagination';
+ /** default options */
+ Pagination.prototype.defaults = {
+ /** which page to begin at */
+ startingPage : 0,
+ /** number of data per page */
+ perPage : 20,
+ /** the total number of data (null == unknown) */
+ totalDataSize : null,
+ /** size of current data on current page */
+ currDataSize : null
+ };
+
+ /** init the control, calc numPages if possible, and render
+ * @param {jQuery} the element that will contain the pagination control
+ * @param {Object} options a map containing overrides to the pagination default options
+ */
+ Pagination.prototype.init = function _init( $element, options ){
+ options = options || {};
+ this.$element = $element;
+ this.options = jQuery.extend( true, {}, this.defaults, options );
+
+ this.currPage = this.options.startingPage;
+ if( this.options.totalDataSize !== null ){
+ this.numPages = Math.ceil( this.options.totalDataSize / this.options.perPage );
+ // limit currPage by numPages
+ if( this.currPage >= this.numPages ){
+ this.currPage = this.numPages - 1;
+ }
+ }
+ //console.debug( 'Pagination.prototype.init:', this.$element, this.currPage );
+ //console.debug( JSON.stringify( this.options ) );
+
+ // bind to data of element
+ this.$element.data( Pagination.prototype.DATA_KEY, this );
+
+ this._render();
+ return this;
+ };
+
+ /** helper to create a simple li + a combo */
+ function _make$Li( contents ){
+ return $([
+ '
', contents, ''
+ ].join( '' ));
+ }
+
+ /** render previous and next pagination buttons */
+ Pagination.prototype._render = function __render(){
+ // no data - no pagination
+ if( this.options.totalDataSize === 0 ){ return this; }
+ // only one page
+ if( this.numPages === 1 ){ return this; }
+
+ // when the number of pages are known, render each page as a link
+ if( this.numPages > 0 ){
+ this._renderPages();
+ this._scrollToActivePage();
+
+ // when the number of pages is not known, render previous or next
+ } else {
+ this._renderPrevNext();
+ }
+ return this;
+ };
+
+ /** render previous and next pagination buttons */
+ Pagination.prototype._renderPrevNext = function __renderPrevNext(){
+ var pagination = this,
+ $prev = _make$Li( 'Prev' ),
+ $next = _make$Li( 'Next' ),
+ $paginationContainer = $( '' ).addClass( 'pagination pagination-prev-next' );
+
+ // disable if it either end
+ if( this.currPage === 0 ){
+ $prev.addClass( 'disabled' );
+ } else {
+ $prev.click( function(){ pagination.prevPage(); });
+ }
+ if( ( this.numPages && this.currPage === ( this.numPages - 1 ) )
+ || ( this.options.currDataSize && this.options.currDataSize < this.options.perPage ) ){
+ $next.addClass( 'disabled' );
+ } else {
+ $next.click( function(){ pagination.nextPage(); });
+ }
+
+ this.$element.html( $paginationContainer.append([ $prev, $next ]) );
+ //console.debug( this.$element, this.$element.html() );
+ return this.$element;
+ };
+
+ /** render page links for each possible page (if we can) */
+ Pagination.prototype._renderPages = function __renderPages(){
+ // it's better to scroll the control and let the user see all pages
+ // than to force her/him to change pages in order to find the one they want (as traditional << >> does)
+ var pagination = this,
+ $scrollingContainer = $( '' ).addClass( 'pagination-scroll-container' ),
+ $paginationContainer = $( '
' ).addClass( 'pagination pagination-page-list' ),
+ page$LiClick = function( ev ){
+ pagination.goToPage( $( this ).data( 'page' ) );
+ };
+
+ for( var i=0; i
= this.numPages ){ page = this.numPages - 1; }
+ if( page === this.currPage ){ return this; }
+
+ //console.debug( '\t going to page ' + page )
+ this.currPage = page;
+ this.$element.trigger( 'pagination.page-change', this.currPage );
+ //console.info( 'pagination:page-change', this.currPage );
+ this._render();
+ return this;
+ };
+
+ /** go to the previous page */
+ Pagination.prototype.prevPage = function prevPage(){
+ return this.goToPage( this.currPage - 1 );
+ };
+
+ /** go to the next page */
+ Pagination.prototype.nextPage = function nextPage(){
+ return this.goToPage( this.currPage + 1 );
+ };
+
+ /** return the current page */
+ Pagination.prototype.page = function page(){
+ return this.currPage;
+ };
+
+ // alternate constructor invocation
+ Pagination.create = function _create( $element, options ){
+ return new Pagination( $element, options );
+ };
+
+ // as jq plugin
+ jQuery.fn.extend({
+ pagination : function $pagination( options ){
+ var nonOptionsArgs = jQuery.makeArray( arguments ).slice( 1 );
+
+ // if passed an object - use that as an options map to create pagination for each selected
+ if( jQuery.type( options ) === 'object' ){
+ return this.map( function(){
+ Pagination.create( $( this ), options );
+ return this;
+ });
+ }
+
+ // (other invocations only work on the first element in selected)
+ var $firstElement = $( this[0] ),
+ previousControl = $firstElement.data( Pagination.prototype.DATA_KEY );
+ // if a pagination control was found for this element, either...
+ if( previousControl ){
+ // invoke a function on the pagination object if passed a string (the function name)
+ if( jQuery.type( options ) === 'string' ){
+ var fn = previousControl[ options ];
+ if( jQuery.type( fn ) === 'function' ){
+ return fn.apply( previousControl, nonOptionsArgs );
+ }
+
+ // if passed nothing, return the previously set control
+ } else {
+ return previousControl;
+ }
+ }
+ // if there is no control already set, return undefined
+ return undefined;
+ }
+ });
+}));
diff --git a/config/plugins/visualizations/scatterplot/templates/scatterplot.mako b/config/plugins/visualizations/scatterplot/templates/scatterplot.mako
index 666ead2c87d..77e2e92092f 100644
--- a/config/plugins/visualizations/scatterplot/templates/scatterplot.mako
+++ b/config/plugins/visualizations/scatterplot/templates/scatterplot.mako
@@ -33,6 +33,7 @@ ${h.js( 'libs/jquery/jquery',
'libs/handlebars.runtime',
'mvc/ui',
'jq-plugins/ui/peek-column-selector',
+ 'jq-plugins/ui/pagination',
'mvc/visualization/visualization-model' )}
${h.javascript_link( root + 'plugins/visualizations/scatterplot/static/scatterplot-edit.js' )}
diff --git a/static/scripts/jq-plugins/ui/pagination.js b/static/scripts/jq-plugins/ui/pagination.js
new file mode 100644
index 00000000000..a0f13183703
--- /dev/null
+++ b/static/scripts/jq-plugins/ui/pagination.js
@@ -0,0 +1,226 @@
+// from: https://raw.githubusercontent.com/umdjs/umd/master/jqueryPlugin.js
+// Uses AMD or browser globals to create a jQuery plugin.
+(function (factory) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module.
+ define(['jquery'], factory);
+ } else {
+ // Browser globals
+ factory(jQuery);
+ }
+
+}(function ($) {
+
+ /** Builds (twitter bootstrap styled) pagination controls.
+ * If the totalDataSize is not null, a horizontal list of page buttons is displayed.
+ * If totalDataSize is null, two links ('Prev' and 'Next) are displayed.
+ * When pages are changed, a 'pagination.page-change' event is fired
+ * sending the event and the (0-based) page requested.
+ */
+ function Pagination( element, options ){
+ /** the total number of pages */
+ this.numPages = null;
+ /** the current, active page */
+ this.currPage = 0;
+ return this.init( element, options );
+ }
+
+ /** data key under which this object will be stored in the element */
+ Pagination.prototype.DATA_KEY = 'pagination';
+ /** default options */
+ Pagination.prototype.defaults = {
+ /** which page to begin at */
+ startingPage : 0,
+ /** number of data per page */
+ perPage : 20,
+ /** the total number of data (null == unknown) */
+ totalDataSize : null,
+ /** size of current data on current page */
+ currDataSize : null
+ };
+
+ /** init the control, calc numPages if possible, and render
+ * @param {jQuery} the element that will contain the pagination control
+ * @param {Object} options a map containing overrides to the pagination default options
+ */
+ Pagination.prototype.init = function _init( $element, options ){
+ options = options || {};
+ this.$element = $element;
+ this.options = jQuery.extend( true, {}, this.defaults, options );
+
+ this.currPage = this.options.startingPage;
+ if( this.options.totalDataSize !== null ){
+ this.numPages = Math.ceil( this.options.totalDataSize / this.options.perPage );
+ // limit currPage by numPages
+ if( this.currPage >= this.numPages ){
+ this.currPage = this.numPages - 1;
+ }
+ }
+ //console.debug( 'Pagination.prototype.init:', this.$element, this.currPage );
+ //console.debug( JSON.stringify( this.options ) );
+
+ // bind to data of element
+ this.$element.data( Pagination.prototype.DATA_KEY, this );
+
+ this._render();
+ return this;
+ };
+
+ /** helper to create a simple li + a combo */
+ function _make$Li( contents ){
+ return $([
+ '', contents, ''
+ ].join( '' ));
+ }
+
+ /** render previous and next pagination buttons */
+ Pagination.prototype._render = function __render(){
+ // no data - no pagination
+ if( this.options.totalDataSize === 0 ){ return this; }
+ // only one page
+ if( this.numPages === 1 ){ return this; }
+
+ // when the number of pages are known, render each page as a link
+ if( this.numPages > 0 ){
+ this._renderPages();
+ this._scrollToActivePage();
+
+ // when the number of pages is not known, render previous or next
+ } else {
+ this._renderPrevNext();
+ }
+ return this;
+ };
+
+ /** render previous and next pagination buttons */
+ Pagination.prototype._renderPrevNext = function __renderPrevNext(){
+ var pagination = this,
+ $prev = _make$Li( 'Prev' ),
+ $next = _make$Li( 'Next' ),
+ $paginationContainer = $( '' ).addClass( 'pagination pagination-prev-next' );
+
+ // disable if it either end
+ if( this.currPage === 0 ){
+ $prev.addClass( 'disabled' );
+ } else {
+ $prev.click( function(){ pagination.prevPage(); });
+ }
+ if( ( this.numPages && this.currPage === ( this.numPages - 1 ) )
+ || ( this.options.currDataSize && this.options.currDataSize < this.options.perPage ) ){
+ $next.addClass( 'disabled' );
+ } else {
+ $next.click( function(){ pagination.nextPage(); });
+ }
+
+ this.$element.html( $paginationContainer.append([ $prev, $next ]) );
+ //console.debug( this.$element, this.$element.html() );
+ return this.$element;
+ };
+
+ /** render page links for each possible page (if we can) */
+ Pagination.prototype._renderPages = function __renderPages(){
+ // it's better to scroll the control and let the user see all pages
+ // than to force her/him to change pages in order to find the one they want (as traditional << >> does)
+ var pagination = this,
+ $scrollingContainer = $( '' ).addClass( 'pagination-scroll-container' ),
+ $paginationContainer = $( '
' ).addClass( 'pagination pagination-page-list' ),
+ page$LiClick = function( ev ){
+ pagination.goToPage( $( this ).data( 'page' ) );
+ };
+
+ for( var i=0; i
= this.numPages ){ page = this.numPages - 1; }
+ if( page === this.currPage ){ return this; }
+
+ //console.debug( '\t going to page ' + page )
+ this.currPage = page;
+ this.$element.trigger( 'pagination.page-change', this.currPage );
+ //console.info( 'pagination:page-change', this.currPage );
+ this._render();
+ return this;
+ };
+
+ /** go to the previous page */
+ Pagination.prototype.prevPage = function prevPage(){
+ return this.goToPage( this.currPage - 1 );
+ };
+
+ /** go to the next page */
+ Pagination.prototype.nextPage = function nextPage(){
+ return this.goToPage( this.currPage + 1 );
+ };
+
+ /** return the current page */
+ Pagination.prototype.page = function page(){
+ return this.currPage;
+ };
+
+ // alternate constructor invocation
+ Pagination.create = function _create( $element, options ){
+ return new Pagination( $element, options );
+ };
+
+ // as jq plugin
+ jQuery.fn.extend({
+ pagination : function $pagination( options ){
+ var nonOptionsArgs = jQuery.makeArray( arguments ).slice( 1 );
+
+ // if passed an object - use that as an options map to create pagination for each selected
+ if( jQuery.type( options ) === 'object' ){
+ return this.map( function(){
+ Pagination.create( $( this ), options );
+ return this;
+ });
+ }
+
+ // (other invocations only work on the first element in selected)
+ var $firstElement = $( this[0] ),
+ previousControl = $firstElement.data( Pagination.prototype.DATA_KEY );
+ // if a pagination control was found for this element, either...
+ if( previousControl ){
+ // invoke a function on the pagination object if passed a string (the function name)
+ if( jQuery.type( options ) === 'string' ){
+ var fn = previousControl[ options ];
+ if( jQuery.type( fn ) === 'function' ){
+ return fn.apply( previousControl, nonOptionsArgs );
+ }
+
+ // if passed nothing, return the previously set control
+ } else {
+ return previousControl;
+ }
+ }
+ // if there is no control already set, return undefined
+ return undefined;
+ }
+ });
+}));
diff --git a/static/scripts/packed/jq-plugins/ui/pagination.js b/static/scripts/packed/jq-plugins/ui/pagination.js
new file mode 100644
index 00000000000..f46909fbb31
--- /dev/null
+++ b/static/scripts/packed/jq-plugins/ui/pagination.js
@@ -0,0 +1 @@
+(function(a){if(typeof define==="function"&&define.amd){define(["jquery"],a)}else{a(jQuery)}}(function(c){function k(p,o){this.numPages=null;this.currPage=0;return this.init(p,o)}k.prototype.DATA_KEY="pagination";k.prototype.defaults={startingPage:0,perPage:20,totalDataSize:null,currDataSize:null};k.prototype.init=function h(o,p){p=p||{};this.$element=o;this.options=jQuery.extend(true,{},this.defaults,p);this.currPage=this.options.startingPage;if(this.options.totalDataSize!==null){this.numPages=Math.ceil(this.options.totalDataSize/this.options.perPage);if(this.currPage>=this.numPages){this.currPage=this.numPages-1}}this.$element.data(k.prototype.DATA_KEY,this);this._render();return this};function n(o){return c(['',o,""].join(""))}k.prototype._render=function f(){if(this.options.totalDataSize===0){return this}if(this.numPages===1){return this}if(this.numPages>0){this._renderPages();this._scrollToActivePage()}else{this._renderPrevNext()}return this};k.prototype._renderPrevNext=function b(){var p=this,q=n("Prev"),o=n("Next"),r=c("").addClass("pagination pagination-prev-next");if(this.currPage===0){q.addClass("disabled")}else{q.click(function(){p.prevPage()})}if((this.numPages&&this.currPage===(this.numPages-1))||(this.options.currDataSize&&this.options.currDataSize").addClass("pagination-scroll-container"),t=c("").addClass("pagination pagination-page-list"),s=function(u){o.goToPage(c(this).data("page"))};for(var p=0;p=this.numPages){o=this.numPages-1}if(o===this.currPage){return this}this.currPage=o;this.$element.trigger("pagination.page-change",this.currPage);this._render();return this};k.prototype.prevPage=function d(){return this.goToPage(this.currPage-1)};k.prototype.nextPage=function i(){return this.goToPage(this.currPage+1)};k.prototype.page=function g(){return this.currPage};k.create=function l(o,p){return new k(o,p)};jQuery.fn.extend({pagination:function e(p){var o=jQuery.makeArray(arguments).slice(1);if(jQuery.type(p)==="object"){return this.map(function(){k.create(c(this),p);return this})}var r=c(this[0]),s=r.data(k.prototype.DATA_KEY);if(s){if(jQuery.type(p)==="string"){var q=s[p];if(jQuery.type(q)==="function"){return q.apply(s,o)}}else{return s}}return undefined}})}));
\ No newline at end of file
diff --git a/static/style/blue/base.css b/static/style/blue/base.css
index f6acf1d89c8..bb21eb1212b 100644
--- a/static/style/blue/base.css
+++ b/static/style/blue/base.css
@@ -1642,12 +1642,6 @@ div.unified-panel-body{position:absolute;top:30px;bottom:0;width:100%}
.quota-meter-bar-warn{background-color:#bf822c}
.quota-meter-bar-error{background-color:#b93e3a}
.quota-meter-text{position:absolute;top:50%;left:0;width:100px;height:16px;margin-top:-6px;text-align:center;z-index:9001;color:#000;white-space:nowrap}
-.pagination{margin:0px}
-.pagination-scroll-container{display:inline-block;background-color:#F8F8F8;border-radius:3px;border:1px solid #BFBFBF;overflow:auto}
-.pagination-scroll-container .pagination-page-list{margin:3px 0px 3px 0px}
-.pagination-scroll-container .pagination-page-list>li:first-child>a,.pagination-scroll-container .pagination-page-list>li:first-child>span{border-radius:0px;border-left:0px}
-.pagination-scroll-container .pagination-page-list>li:last-child>a,.pagination-scroll-container .pagination-page-list>li:last-child>span{border-radius:0px}
-.pagination-scroll-container .pagination-page-list>li>a{float:none;position:static;border:1px solid #BFBFBF;border-width:0px 0px 0px 1px}
div.metadataForm{border:solid #aaaaaa 1px}
div.metadataFormTitle{font-weight:bold;padding:5px;padding-left:10px;padding-right:10px;background:#cccccc;background-repeat:repeat-x;background-position:top;border-bottom:solid #aaaaaa 1px}
div.metadataFormBody{background:#FFFFFF;padding:5px 0}
@@ -2112,6 +2106,12 @@ pre.peek table,pre.peek th,pre.peek tr,pre.peek td{font-family:Monaco,Menlo,Cons
.peek-column-selector .control td:hover .button{background-color:#EEE;border:1px solid black;cursor:pointer;color:black}
.peek-column-selector .control td.disabled .button,.peek-column-selector .control td.disabled:hover .button{background-color:transparent;border:1px solid #CCC;cursor:not-allowed;color:#CCC}
.peek-column-selector .control td.selected .button{background-color:black;border:1px solid black;color:white}
+.pagination{margin:0px}
+.pagination-scroll-container{display:inline-block;background-color:#F8F8F8;border-radius:3px;border:1px solid #BFBFBF;overflow:auto}
+.pagination-scroll-container .pagination-page-list{margin:3px 0px 3px 0px}
+.pagination-scroll-container .pagination-page-list>li:first-child>a,.pagination-scroll-container .pagination-page-list>li:first-child>span{border-radius:0px;border-left:0px}
+.pagination-scroll-container .pagination-page-list>li:last-child>a,.pagination-scroll-container .pagination-page-list>li:last-child>span{border-radius:0px}
+.pagination-scroll-container .pagination-page-list>li>a{float:none;position:static;border:1px solid #BFBFBF;border-width:0px 0px 0px 1px}
.toolMenuContainer{color:#000;background:#dfe5f9;min-height:100%;padding:5px 10px}
div.toolSectionPad{margin:0;padding:0;height:5px;font-size:0px}
div.toolSectionWrapper{margin-bottom:5px}
diff --git a/static/style/src/less/base.less b/static/style/src/less/base.less
index ec7a639a284..923f1eb107d 100644
--- a/static/style/src/less/base.less
+++ b/static/style/src/less/base.less
@@ -509,42 +509,6 @@ div.unified-panel-body {
white-space: nowrap;
}
-// ---------------------------------------------------------------------------- pagination & scrolling pagination
-.pagination {
- margin: 0px;
-}
-
-.pagination-scroll-container {
- display : inline-block;
- background-color: #F8F8F8;
- border-radius : 3px;
- border : 1px solid #BFBFBF;
- overflow : auto;
-}
-
-.pagination-scroll-container .pagination-page-list {
- margin : 3px 0px 3px 0px;
-}
-
-.pagination-scroll-container .pagination-page-list > li:first-child > a,
-.pagination-scroll-container .pagination-page-list > li:first-child > span {
- border-radius : 0px;
- border-left : 0px;
-}
-
-.pagination-scroll-container .pagination-page-list > li:last-child > a,
-.pagination-scroll-container .pagination-page-list > li:last-child > span {
- border-radius : 0px;
-}
-
-.pagination-scroll-container .pagination-page-list > li > a {
- float : none;
- position : static;
- border : 1px solid #BFBFBF;
- border-width : 0px 0px 0px 1px;
-}
-
-
// ==== Tool form styles ====
div.metadataForm {
@@ -1495,6 +1459,7 @@ div.permissionContainer {
@import "ui/dataset-choice.less";
@import "ui/peek-column-selector.less";
+@import "ui/pagination.less";
// ==== Tool menu styles
diff --git a/static/style/src/less/ui/pagination.less b/static/style/src/less/ui/pagination.less
new file mode 100644
index 00000000000..735f849a58c
--- /dev/null
+++ b/static/style/src/less/ui/pagination.less
@@ -0,0 +1,34 @@
+// pagination & scrolling pagination: see scripts/jq-plugins/ui/pagination.js
+.pagination {
+ margin: 0px;
+}
+
+.pagination-scroll-container {
+ display : inline-block;
+ background-color: #F8F8F8;
+ border-radius : 3px;
+ border : 1px solid #BFBFBF;
+ overflow : auto;
+}
+
+.pagination-scroll-container .pagination-page-list {
+ margin : 3px 0px 3px 0px;
+}
+
+.pagination-scroll-container .pagination-page-list > li:first-child > a,
+.pagination-scroll-container .pagination-page-list > li:first-child > span {
+ border-radius : 0px;
+ border-left : 0px;
+}
+
+.pagination-scroll-container .pagination-page-list > li:last-child > a,
+.pagination-scroll-container .pagination-page-list > li:last-child > span {
+ border-radius : 0px;
+}
+
+.pagination-scroll-container .pagination-page-list > li > a {
+ float : none;
+ position : static;
+ border : 1px solid #BFBFBF;
+ border-width : 0px 0px 0px 1px;
+}
diff --git a/static/style/src/less/ui/peek-column-selector.less b/static/style/src/less/ui/peek-column-selector.less
index db9c7a8360b..a8963a79140 100644
--- a/static/style/src/less/ui/peek-column-selector.less
+++ b/static/style/src/less/ui/peek-column-selector.less
@@ -1,4 +1,4 @@
-// peek-based column chooser, see: scripts/ui/peek-column-selector.js
+// peek-based column chooser, see: scripts/jq-plugins/ui/peek-column-selector.js
.peek-column-selector {
border-radius: 3px;
border: 1px solid rgb(95, 105, 144);