Advanced custom paging

Create re-usable controls that are registered with FooGallery.

The basic example works well and will probably be the way the majority of users implement custom paging. If however you intend to re-use your controls across multiple galleries, pages or projects you may want to turn it into a paging type that is then registered with FooGallery. This allows you to use your controls by simply setting the type option to the name you registered it with.

In this example we take the custom paging controls and convert them into a re-usable paging type that can be included on any page after the FooGallery files. We also extend the functionality by adding in support for the theme and position options as well as adding in a label that displays the relevant page information.

The first thing we change when making the basic example controls re-usable is the CSS. As it stands the color properties are all mixed in with the layout properties of the various classes. Instead we want our re-usable control to support the theme option and its' two basic themes "fg-light" and "fg-dark". To do this we need to split all the colors out and by default only apply basic layout and positioning properties in the classes.

.custom-paging {
	box-sizing: border-box;
	display: block;
	padding: 15px;
	text-align: center;
	margin-bottom: 20px;
	vertical-align: middle;
}
.custom-paging:after {
	display: table;
	content: "";
	clear: both;
}
.custom-label {
	font-weight: normal;
	line-height: 40px;
	margin: 0;
}
.custom-btn {
	margin: 0;
	min-width: 80px;
	padding: 10px 15px;
}
.custom-prev {
	float: left;
}
.custom-next {
	float: right;
}

Supporting the light theme

Now that the controls have there basic layout and positioning taken care of we can add in support for the two basic themes, the first being "fg-light". The light theme is intended to be used on light background colors, the original colors we just stripped from the layout classes can now be used to create it.

.custom-paging.fg-light {
	background-color: #eee;
	border: solid 1px #ddd;
}
.fg-light .custom-label {
	color: #555;
}
.fg-light .custom-btn {
	color: #555;
	border: solid 1px #ccc;
	background-color: #fbfbfb;
}
.fg-light .custom-btn:hover,
.fg-light .custom-btn:focus {
	color: #333;
	border: solid 1px #a8a8a8;
	background-color: #FFF;
}

Supporting the dark theme

Now that we have a light theme we need to support its' alternate "fg-dark" theme which is intended to be used on dark background colors.

.custom-paging.fg-dark {
	background-color: #333;
	border: solid 1px #222;
}
.fg-dark .custom-label {
	color: #ddd;
}
.fg-dark .custom-btn {
	color: #ddd;
	border: solid 1px #333;
	background-color: #444;
}
.fg-dark .custom-btn:hover,
.fg-dark .custom-btn:focus {
	color: #fff;
	border: solid 1px #444;
	background-color: #555;
}

Instead of simply binding to events like we did in the basic example here we inherit from the FooGallery.Paging and FooGallery.PagingControl classes, make our modifications and then register them with FooGallery as a new type using our own name.

Where to start?

We begin by creating a new scope to use for our custom type to avoid polluting the global one. It also gives us the opportunity to alias the required jQuery and FooGallery objects to $ and _ respectively.

(function($, _){

	// create our type here

})(jQuery, FooGallery);

Figuring out the configuration

Let's look at the raw HTML of our desired custom controls keeping in mind we want to make it as configurable as possible to allow for easy re-use.

<nav class="custom-paging">
	<button class="custom-btn custom-prev" type="button">Previous</button>
	<label class="custom-label">Showing 1 of 10</label>
	<button class="custom-btn custom-next" type="button">Next</button>
</nav>

CSS classes

From the above HTML we extract the CSS classes into an object that contains a property for each element. This object is merged with the default classes and exposed as the cls property when an instance of our custom type is created.

var classes = {
	container: "custom-paging", // overrides the default so we can use the base create method
	prev: "custom-btn custom-prev",
	next: "custom-btn custom-next",
	label: "custom-label"
};

Localization

We can also see that we need to expose three strings for translation, two for the Previous and Next buttons and another for the label. The label requires information that we do not have when it is configured so we add in some placeholders for the real values. To do this we create an object with a property for each of these strings. This object is merged with the default strings and exposed as the il8n property when an instance of our custom type is created.

var il8n = {
	prev: "Previous",
	next: "Next",
	label: "Showing [current] of [total]"
};

Options

There are no custom options for our controls however we do want to change the default position value to "top". To do this we create an object containing just that property as it is merged with the default paging options and exposed as the opt property when an instance of our custom type is created.

var options = {
	position: "top"
};

Why break it down like this?

As said before we want to make our controls as configurable as possible as the next time we use it we may want to add an additional class to one of the elements or change the text of the label. There are two ways to configure a registered type; globally which changes the defaults for all new instances and then per instance which changes just that single instance.

To configure a registered types defaults you can use the FooGallery.paging.configure(name, options, classes, il8n) method. In the below example we change the default position from "top" to "both", add an extra CSS class to the label and then change its' string from "Showing [current] of [total]" to "[current]/[total]".

FooGallery.paging.configure("custom", {
	position: "both"
}, {
	label: "custom-label extra-class"
}, {
	label: "[current]/[total]"
});

The second way is simply providing the options when a new template is constructed. In the below example we change the values as seen above however we do it at the instance level.

$(".foogallery").foogallery({
	paging: {
		type: "custom",
		position: "both"
	},
	cls: {
		paging: {
			label: "custom-label extra-class"
		}
	},
	il8n: {
		paging: {
			label: "[current]/[total]"
		}
	}
});

Now that we have our basic configuration objects it's time to put them to use. The two classes we will be working with are the FooGallery.Paging and FooGallery.PagingControl classes. These two classes provide the foundation and core logic on top of which all paging types for FooGallery are built.

Paging class

To create a new paging type we need to inherit from the FooGallery.Paging class. This class contains all the core logic to handle paging and allows us to override the default behavior or expose any custom methods we may need. This base class also contains the logic that calls the various methods of the FooGallery.PagingControl class.

var Paging = _.Paging.extend({
	// extend the base object with any methods or properties you want
	// this object is exposed as the '.pages' property of the current
	// template and paging control objects
});

In this example we don't actually create any additional properties or methods so we can leave it just like the above. If however you did want to add in some custom members you could do so like the below.

var Paging = _.Paging.extend({
	// override the base construct method to set our own properties
	construct: function(template){
		// call the base method so it can perform its' setup logic
		this._super(template);
		// add a custom property here
		this.someProperty = false;
	},
	// extend the base object with a method
	doSomething: function(){
		if (this.someProperty){
			// do something
		}
	}
});

PagingControl class

To create a new paging control we need to inherit from the FooGallery.PagingControl class. This class contains the majority of our customizations as we are simply creating custom controls for our type and not changing any default behavior. To do this we make use of the base $container property and the create, destroy and update methods. We also create a utility method for retrieving a formatted version of the label configuration string.

As previously mentioned the localized strings are exposed as the il8n property of the FooGallery.Paging class which is then exposed on the control class as the pages property. This means we can access our custom strings using this.pages.il8n when within the context of the control.


label method

The first method we create is the custom label utility method which simply provides us a formatted version of the label configuration string.

// extend the base object with a new label method that returns the text to display
label: function(pageNumber){
	// instead of hard-coding the format string we supply it as a localized string when
	// registering the paging type so it can be changed easily.
	return this.pages.il8n.label
		.replace(/\[current]/g, pageNumber)
		.replace(/\[total]/g, this.pages.total);
}

create method

The next method we create is the create method. This method is called by the parent FooGallery.Paging class whenever it needs to create the elements for the custom control. The base method populates the $container property so we still want to execute that. We get back an element with the correct custom-paging CSS class as we override the default container CSS class.

// override the base create method so we can also create our custom elements
create: function(){
	// call the base method
	if (this._super()){
		// the '$container' was created so create our custom elements
		return true;
	}
	return false;
}

At this point the elements created using the above method would just be the container as seen below.

<nav class="custom-paging">
	<!-- We still need to create our inner elements -->
</nav>

So let's create the inner elements making use of the CSS classes and localized strings provided by the configuration.

// override the base create method so we can also create our custom elements
create: function(){
	// call the base method
	if (this._super()){
		// the '$container' was created so create our custom elements.
		// first hold a reference to the parent paging class to use within the button
		// event listeners as 'this' refers to the element the event occured on.
		var pages = this.pages,
			// the custom classes have been merged and are exposed as the 'pages.cls' property
			classes = pages.cls,
			// the custom strings have been merged and are exposed as the 'pages.il8n' property
			il8n = pages.il8n;

		// then create the custom previous, next and label elements
		this.$prev = $("<button/>", {
			"class": classes.prev,
			"type": "button",
			"text": il8n.prev
		}).on("click.custom-paging", function(e){
			// first stop any default behavior
			e.preventDefault();
			// then tell the parent paging class to show the previous page
			pages.prev();
		});

		this.$next = $("<button/>", {
			"class": classes.next,
			"type": "button",
			"text": il8n.next
		}).on("click.custom-paging", function(e){
			// first stop any default behavior
			e.preventDefault();
			// then tell the parent paging class to show the next page
			pages.next();
		});

		// when creating the label we get the text by calling the 'label' utility method passing
		// in the current page number
		this.$label = $("<label/>", {
			"class": classes.label,
			"text": this.label(pages.current)
		});

		// then add them to the container.
		this.$container.append(this.$prev, this.$label, this.$next);

		// true = success; if for some reason you can't or don't want to create the
		// controls' elements you can exit early by returning false.
		return true;
	}
	// something went wrong and no elements were created!
	return false;
}

At this point all our control elements are created and the HTML output for a ten page template would look something like the below.

<nav class="custom-paging">
	<button class="custom-btn custom-prev" type="button">Previous</button>
	<label class="custom-label">Showing 1 of 10</label>
	<button class="custom-btn custom-next" type="button">Next</button>
</nav>

update method

OK, now we're getting somewhere but the create method is not called when ever the page changes so how can we update the label? We can do it by overriding the update method which is called by the parent FooGallery.Paging class and is supplied the new page number as it's only argument whenever the controls need to update there elements.

// override the base update method so we can update the label
update: function(pageNumber){
	// get the formatted localized string using our utility method
	var text = this.label(pageNumber);
	// then simply update the $label's text
	this.$label.text(text);

	// if you wanted to this is where you would add in logic to enable or disable the buttons
	// depending on the current page number.
}

destroy method

Finally we need to ensure that we cleanup up after ourselves preparing our custom type for garbage collection. We do this by overriding the destroy method and performing any required work. In this case we need to unbind our events and release some of the properties to ensure the object is disposed of correctly.

// override the base destroy method
destroy: function(){
	// cleanup our custom elements unbinding events, removing the elements and resetting any properties
	// basically preparing this object for garbage collection.
	this.$prev.add(this.$next).off(".custom-paging").add(this.$label).remove();
	this.$prev = this.$next = this.$label = null;
	// call the base destroy method that amongst other things cleans up the $container element
	this._super();
}

Registering the custom type

All right, we now have our custom classes and the configuration objects that go with them, how do we register them with FooGallery? Quite simply actually, you just need to call the FooGallery.paging.register(name, klass, ctrl, options, classes, il8n) method supplying it the various arguments.

// Now that we have all the parts we need register the type with FooGallery.
_.paging.register("custom", Paging, Control, options, classes, il8n);

That's it, combining all the above CSS and JavaScript examples we get the following completed custom paging type which can be included on any page across projects.

.custom-paging {
	box-sizing: border-box;
	display: block;
	padding: 15px;
	text-align: center;
	margin-bottom: 20px;
	vertical-align: middle;
}
.custom-paging:after {
	display: table;
	content: "";
	clear: both;
}
.custom-label {
	font-weight: normal;
	line-height: 40px;
	margin: 0;
}
.custom-btn {
	margin: 0;
	min-width: 80px;
	padding: 10px 15px;
}
.custom-prev {
	float: left;
}
.custom-next {
	float: right;
}


.custom-paging.fg-light {
	background-color: #eee;
	border: solid 1px #ddd;
}
.fg-light .custom-label {
	color: #555;
}
.fg-light .custom-btn {
	color: #555;
	border: solid 1px #ccc;
	background-color: #fbfbfb;
}
.fg-light .custom-btn:hover,
.fg-light .custom-btn:focus {
	color: #333;
	border: solid 1px #a8a8a8;
	background-color: #FFF;
}


.custom-paging.fg-dark {
	background-color: #333;
	border: solid 1px #222;
}
.fg-dark .custom-label {
	color: #ddd;
}
.fg-dark .custom-btn {
	color: #ddd;
	border: solid 1px #333;
	background-color: #444;
}
.fg-dark .custom-btn:hover,
.fg-dark .custom-btn:focus {
	color: #fff;
	border: solid 1px #444;
	background-color: #555;
}
(function($, _){

	var classes = {
		container: "custom-paging",
		prev: "custom-btn custom-prev",
		next: "custom-btn custom-next",
		label: "custom-label"
	};

	var il8n = {
		prev: "Previous",
		next: "Next",
		label: "Showing [current] of [total]"
	};

	var defaults = {
		position: "top"
	};

	var Paging = _.Paging.extend({});

	var Control = _.PagingControl.extend({
		construct: function(template, parent, position){
			this._super(template, parent, position);
			this.$prev = $();
			this.$next = $();
			this.$label = $();
		},
		label: function(pageNumber){
			return this.pages.il8n.label
				.replace(/\[current]/g, pageNumber)
				.replace(/\[total]/g, this.pages.total);
		},
		create: function(){
			if (this._super()){
				var pages = this.pages,
					classes = pages.cls,
					il8n = pages.il8n;

				this.$prev = $("<button/>", {
					"class": classes.prev,
					"type": "button",
					"text": il8n.prev
				}).on("click.custom-paging", function(e){
					e.preventDefault();
					pages.prev();
				});

				this.$next = $("<button/>", {
					"class": classes.next,
					"type": "button",
					"text": il8n.next
				}).on("click.custom-paging", function(e){
					e.preventDefault();
					pages.next();
				});

				this.$label = $("<label/>", {
					"class": classes.label,
					"text": this.label(pages.current)
				});

				this.$container.append(this.$prev, this.$label, this.$next);
				return true;
			}
			return false;
		},
		update: function(pageNumber){
			var text = this.label(pageNumber);
			this.$label.text(text);
		},
		destroy: function(){
			this.$prev.add(this.$next).off(".custom-paging").add(this.$label).remove();
			this.$prev = this.$next = this.$label = null;
			this._super();
		}
	});

	_.paging.register("custom", Paging, Control, defaults, classes, il8n);

})(jQuery, FooGallery);

Once our custom type is included in the page after the FooGallery files we can then use it by setting the type option to "custom".

<!-- The container element -->
<div id="custom_example_1" class="foogallery fg-responsive fg-light fg-hover-external fg-loading-default fg-loaded-fade-in fg-center fg-gutter-15">
	<!-- items-250x250.json -->
</div>
jQuery(function($){
	$("#custom_example_1").foogallery({
		"items": "../content/items-250x250.json",
		"paging": {
			"type": "custom"
		}
	});
});
Notes