FooTable icon indicating copy to clipboard operation
FooTable copied to clipboard

Count and Pager in Table Header

Open jeffconklin opened this issue 8 years ago • 6 comments

Hello,

I use paging and filtering on many large tables and have two thoughts:

  • There is no total count of the records that are included in the current filter/results. Though page says "1 of X pages", being able to also show "1,250 results" somewhere would be very useful.

  • To switch to the next "page" without having to go to the bottom. Though having the full pagination along the bottom is great once at the bottom, having something small at the top would also be useful.

For example: screenshot from 2017-01-05-ft

Maybe I missed a hack for something like this already? Would be nice to just have it built in like how paging and filtering is already built in.

Thanks.

jeffconklin avatar Jan 05 '17 18:01 jeffconklin

Hi @jeffconklin,

I'll see what I can do about rendering the pager in the same row at the top of the table as the filter however it may be a little tricky as the paging component is initialized prior to the filtering and so the filtering row does not exist when the pager is first created.

As for the results there is an option to be able to format the display results called countFormat for the paging component which should be able to do what you want.

Thanks

steveush avatar Jan 12 '17 09:01 steveush

Hi @jeffconklin,

In 3.1.4 I made a slight change to the paging component to make it easier to implement custom paging UI elements by exposing the previously private method that performed the count/label formatting as public and refactoring it a bit. The new method is FooTable.Paging#format(string) and that string can contain any of the supported placeholders:

  • {CP} - The current page number.
  • {TP} - The total number of pages.
  • {PF} - The first row of the current page.
  • {PL} - The last row of the current page.
  • {TR} - The total rows available.

So to get your desired format you could use: "Showing {PF}-{PL} of {TR} results". That said the changes I made do not resolve your primary question of placing a smaller paging widget at the top of the table. To resolve this I created a small custom component called FooTable.PagingWidget but I have not included it in the plugin as of yet as I'm not sure if it should be or not at the moment. The code to implement this custom component is below and maybe we can refine it a bit more and include it in a future version.

CSS

.footable-paging-widget-row {
	text-align: right;
}
.footable-paging-widget-label {
	margin-right: 10px;
}
@media (min-width: 768px) {
	.footable-paging-widget {
		float: left;
	}
}

JS

(function($, F){

	/**
	 * An object containing the Paging Widget options.
	 * @type {object}
	 * @prop {boolean} enabled - Whether or not to enable the paging widget.
	 * @prop {string} countFormat - A string format used to generate the page count text.
	 */
	F.Defaults.prototype.paging.widget = {
		enabled: false,
		countFormat: "Showing {PF}-{PL} of {TR} results"
	};

	F.PagingWidget = F.Component.extend(/** @lends FooTable.Component */{
		/**
		 * The constructor for the Paging Widget.
		 * @constructs
		 * @extends FooTable.Component
		 * @param {FooTable.Table} table - The parent {@link FooTable.Table} object for the component.
		 */
		construct: function (table) {
			// call the base constructor
			this._super(table, table.o.paging.widget.enabled);
			/**
			 * The string format used to generate the page count text.
			 * @type {string}
			 */
			this.countFormat = table.o.paging.widget.countFormat;
			/**
			 * A reference to the FooTable.Paging component for this instance of the plugin.
			 * @type {?FooTable.Paging}
			 */
			this.paging = null;
			/**
			 * A reference to the FooTable.Filtering component for this instance of the plugin.
			 * @type {?FooTable.Filtering}
			 */
			this.filtering = null;
			/**
			 * Whether or not filtering is included and enabled for this instance of the plugin.
			 * @type {boolean}
			 */
			this.filtered = false;
			/**
			 * The jQuery TR object the widget uses for its elements.
			 * @type {?jQuery}
			 */
			this.$row = null;
			/**
			 * The jQuery TH object the widget uses for its elements.
			 * @type {?jQuery}
			 */
			this.$cell = null;
			/**
			 * The jQuery DIV object the widget uses to wrap its elements.
			 * @type {?jQuery}
			 */
			this.$container = null;
			/**
			 * The jQuery SPAN object the widget uses to display the current page or row count specified by the {@link FooTable.PagingWidget#countFormat} value.
			 * @type {?jQuery}
			 */
			this.$count = null;
			/**
			 * The jQuery BUTTON object the widget uses for its previous button.
			 * @type {?jQuery}
			 */
			this.$prev = null;
			/**
			 * The jQuery BUTTON object the widget uses for its next button.
			 * @type {?jQuery}
			 */
			this.$next = null;
		},
		/**
		 * Checks the supplied data and options for the paging widget component.
		 * @instance
		 * @param {object} data - The jQuery data object from the parent table.
		 */
		preinit: function (data) {
			this.paging = this.ft.use(F.Paging);
			this.filtering = this.ft.use(F.Filtering);
			this.enabled = F.is.boolean(data.pagingWidget)
				? data.pagingWidget
				: this.enabled && this.paging.enabled;
			if (!this.enabled) return;
			this.filtered = this.filtering && this.filtering.enabled;
			this.countFormat = !F.is.emptyString(data.pagingWidgetCountFormat) ? data.pagingWidgetCountFormat : this.countFormat;
		},
		/**
		 * Initializes the paging widget component for the plugin.
		 * @instance
		 */
		init: function () {
			if (this.filtered){
				// if the filtering component is included and enabled use its' row and form
				this.$row = this.filtering.$row.append('footable-paging-widget-row');
				this.$cell = this.filtering.$cell;
				this.$container = $('<div/>', {'class': 'form-group footable-paging-widget'}).appendTo(this.$cell.find('.form-inline'));
			} else {
				// otherwise create our own for the component
				this.$row = $('<tr/>', {'class': 'footable-paging-widget-row'}).prependTo(this.ft.$el.children('thead'));
				this.$cell = $('<td/>').appendTo(this.$row);
				this.$container = $('<div/>', {'class': 'footable-paging-widget'}).appendTo(this.$cell);
			}
			var $prevIcon = $('<span/>', {'class': 'glyphicon glyphicon-menu-left'});
			var $nextIcon = $('<span/>', {'class': 'glyphicon glyphicon-menu-right'});
			this.$count = $('<span/>', {'class': 'footable-paging-widget-label'});
			this.$prev = $('<button/>', {'class': 'btn btn-default', type: 'button'}).on('click', {self: this}, this.onPrevClick).append($prevIcon);
			this.$next = $('<button/>', {'class': 'btn btn-default', type: 'button'}).on('click', {self: this}, this.onNextClick).append($nextIcon);
			this.$group = $('<div/>', {'class': 'btn-group'}).append(this.$prev, this.$next);
			this.$container.append(this.$count, this.$group);
		},
		/**
		 * Destroys the paging widget component removing all traces of it from the DOM.
		 */
		destroy: function () {
			this.$container.remove();
			if (this.filtered){
				// if the filtering component is included and enabled just remove the custom widget class from the row
				this.$row.removeClass('footable-paging-widget-row');
			} else {
				// otherwise remove the whole row
				this.$row.remove();
			}
		},
		/**
		 * Updates the paging widget component UI.
		 * @instance
		 */
		draw: function () {
			this.$count.text(this.paging.format(this.countFormat));
			if (!this.filtered){
				// if we created our own row we need to update the colspan otherwise it is left up to the filtering component
				this.$cell.attr('colspan', this.ft.columns.visibleColspan);
			}
		},
		onPrevClick: function(e){
			e.data.self.paging.prev();
		},
		onNextClick: function(e){
			e.data.self.paging.next();
		}
	});

	// register the paging widget component with a priority of 300 so that it executes after the filtering and paging components have.
	F.components.register('paging_widget', F.PagingWidget, 300);

})(jQuery, FooTable);

I would probably work a bit more on the CSS and maybe not use BUTTON elements for the previous and next buttons but the above should provide a good base to start from.

The widget at the moment only has two options; enabled and countFormat that can be supplied through either the constructor as part of the normal options object:

$('.table').footable({
	...
	"paging": {
		"widget": {
			"enabled": true,
			"countFormat": "Showing {PF}-{PL} of {TR} results"
		}
	},
	...
});

Or you can supply them as data attributes using; data-paging-widget and data-paging-widget-count-format:

<table class="table" data-paging="true" data-paging-widget="true" data-paging-widget-count-format="{CP} of {TP}">
	...
</table>

Note: I made the widget only honor it's enabled option if the core paging component is also enabled.

Thanks

steveush avatar Jan 14 '17 18:01 steveush

You sir, are the best. I will do my best to try to work with this solution in the next week or so and see how it works.

Thank you. We all appreciate your hard work, @steveush .

jeffconklin avatar Jan 14 '17 18:01 jeffconklin

Looking good. A few notes (that you have most likely already thought of):

  • this.$row = this.filtering.$row.append('footable-paging-widget-row'); adds the string "footable-paging-widget-row" outside the table.
  • Please consider using your fooicon style classes ("fooicon","fooicon-search") instead of the glyphs, as well as Bootstrap btn classes for the BS version.
  • Please consider having this match more of what you have in the paging below the table. As you mentioned, buttons may not be a good choice, since you use anchors in the full pager. Also, disabling / enabling the buttons/links when there is or is not a page to click to (like the full pager) would be great.

This is a really good addition, and thank you for your hard work.

jeffconklin avatar Jan 18 '17 17:01 jeffconklin

Will have a look:

  1. I think I mis-typed that .append and it should have been .addClass so you could still add custom styling even if the row was the filtering row and not its own one.

  2. I used the glyphs directly as this was more of a proof of concept to get the ball rolling for you as it were and not a final polished solution.

  3. Anchors would be better as well as using the Bootstrap pagination classes to make it match the bottom pagination, but for the purposes of the POC I made it as simple as possible and to match the drawing you provided. The logic for disabling re-enabling buttons would just need to be implemented again within the widget class or I can look into providing some additional properties on the paging class to make life a bit simpler.

Thanks for the feedback.

steveush avatar Jan 18 '17 18:01 steveush

Hello, I've been trying to get the total number of rows returned for a filter, not just the number displayed.

For example my page size is 100 rows and I have a total of 300 rows.

If I run a filter which returns 225 items the total rows displayed are only 100, but the filter found 225 items and paginated the results.

What I want to do is display something like this: "Showing 100 of 225 results"

How can I get the total number of items from the filter results (225). I tried using the data-paging-count-format, but I do not see the information displayed. I tried copying the code above and did not get anything to display either. I tried using "after.ft.filtering" and I still can't figure out how to get the total filter results.

Any help would be greatly appreciated. I've been trying to find something for a few days with no luck.

Thanks!

coros-sanborn avatar Oct 05 '18 00:10 coros-sanborn