Carl Rippon

Building SPAs

Carl Rippon
BlogBooks / CoursesAbout
This site uses cookies. Click here to find out more

Building a Pager Web Component – Part 6: Styling

February 10, 2015
javascript

In the last post, we added events to our pager component. In this post, we will style our pager component.

Encapsulated Styles

At the moment we have the following style declaration in our component.

<style>
  .x-pager-link {
    margin-right: 6px;
    text-decoration: none;
  }
  a.x-pager-link:hover {
    text-decoration: underline;
  }
  .x-pager-text {
    margin-left: 20px;
  }
</style>

The specificity is actually over the top here, because the styles within the shadow DOM of a component are encapsulated - i.e. they don’t leak out into the consuming page. So, let’s change our styling to:

<style>
  a {
    margin-right: 6px;
    text-decoration: none;
    color: blue;
  }
  a:hover {
    text-decoration: underline;
  }
  span {
    margin-left: 20px;
  }
</style>

Notice that we have coloured our pager links blue as well. You will see that the styles apply correctly.

If you add the following markup to the consuming page, you can see that the google link is not green which confirms our pager styling is encapsulated.

<div><a href="www.google.co.uk">google</a></div>

Let’s now add more styling to the pager:

<style>
	div {
		background-color: rgb(241, 243, 241);
		padding: 6px;
		width: 270px;
	}
	a {
		margin-right: 6px;
		text-decoration: none;
		color: blue;
	}
	a:hover {
		text-decoration: underline;
	}
	span {
		margin-left: 20px;
		color: rgb(189, 201, 189);
	}
</style>

If we look at our markup and script, we will see we make use of classes when we don’t really need to - we could just use CSS tag selectors.

<div>
	<a href='#' title='Go to the first page' class='x-pager-link x-pager-first-link'>first</a>
	<a href='#' title='Go to the previous page' class='x-pager-link x-pager-previous-link'>previous</a>
	<a href='#' title='Go to the next page' class='x-pager-link x-pager-next-link'>next</a>
	<a href='#' title='Go to the last page' class='x-pager-link x-pager-last-link'>last</a>
	<span class='x-pager-text'>page x of y</span>
</div>

<script>
	(function () {
		var Proto = Object.create(HTMLElement.prototype),
			importDoc = document.currentScript.ownerDocument,
			root,
			pageNumber = 1,
			pageCount = 0;

		Proto.createdCallback = function() {
			var template = importDoc.querySelector("template"),
				clone = document.importNode(template.content, true);
			root = this.createShadowRoot();
			root.appendChild(clone);

			this._readAttributes();
			this._changePage();
			this._addEventListeners();
		};

		Proto.attributeChangedCallback = function(attrName, oldVal, newVal) {
		    this._readAttributes();
		    if (attrName === "page" || attrName === "page-count") {
		    	this._changePage();
		    }
		};

		Proto.page = function (page) {
			var pageChangedEvent;
			if (page) {
				if (this.pageNumber !== page) {
					this.pageNumber = page;
					this._changePage();

					pageChangedEvent = new CustomEvent("pageChanged", {
						detail: {
							pageNumber: this.pageNumber
						},
						bubbles: true,
						cancelable: false
					});
					this.dispatchEvent(pageChangedEvent);
				}
			}
			return this.pageNumber;
		};

		Proto._readAttributes = function () {
			var pageStr,
				pageCountStr;
			pageStr = this.getAttribute("page");
			if (!pageStr) {
				pageStr = "0";
			}
			this.pageNumber = parseInt(pageStr, 10);
			pageCountStr = this.getAttribute("page-count");
			if (!pageCountStr) {
				pageCountStr = "0";
			}
			this.pageCount = parseInt(pageCountStr, 10);
		};

		Proto._changePage = function () {
			root.querySelector(".x-pager-text").innerHTML = "page " + this.pageNumber + " of " + this.pageCount;
		};

		Proto._linkClick = function (args) {
			var gotoPage;
			if (args.target.innerText === "first") {
				gotoPage = 1;
			} else if (args.target.innerText === "previous") {
				gotoPage = this.pageNumber - 1;
				if (gotoPage < 1) {
					gotoPage = 1;
				}
			} else if (args.target.innerText === "next") {
				gotoPage = this.pageNumber + 1;
				if (gotoPage > this.pageCount) {
					gotoPage = this.pageCount;
				}
			} else if (args.target.innerText === "last") {
				gotoPage = this.pageCount;
			}
			this.page(gotoPage);
		};

		Proto._addEventListeners = function () {
			var links,
				i;
			links = root.querySelectorAll("a.x-pager-link");
			if (links.length > 0) {
				for (i = 0; i < links.length; i = i + 1) {
					links[i].addEventListener("click", this._linkClick.bind(this), false);
				}
			}
		};

		document.registerElement('x-pager', {prototype: Proto});
	})();
</script>

So, let’s change our component to use tag selectors:

<template>
  <style>
    div {
      background-color: rgb(241, 243, 241);
      padding: 6px;
      width: 270px;
    }
    a {
      margin-right: 6px;
      text-decoration: none;
      color: blue;
    }
    a:hover {
      text-decoration: underline;
    }
    span {
      margin-left: 20px;
      color: rgb(189, 201, 189);
    }
  </style>
  <div>
    <a href="#" title="Go to the first page">first</a>
    <a href="#" title="Go to the previous page">previous</a>
    <a href="#" title="Go to the next page">next</a>
    <a href="#" title="Go to the last page">last</a>
    <span class="x-pager-text">page x of y</span>
  </div>
</template>

<script>
  (function() {
    var Proto = Object.create(HTMLElement.prototype),
      importDoc = document.currentScript.ownerDocument,
      root,
      pageNumber = 1,
      pageCount = 0;

    Proto.createdCallback = function() {
      var template = importDoc.querySelector("template"),
        clone = document.importNode(template.content, true);
      root = this.createShadowRoot();
      root.appendChild(clone);

      this._readAttributes();
      this._changePage();
      this._addEventListeners();
    };

    Proto.attributeChangedCallback = function(attrName, oldVal, newVal) {
      this._readAttributes();
      if (attrName === "page" || attrName === "page-count") {
        this._changePage();
      }
    };

    Proto.page = function(page) {
      var pageChangedEvent;
      if (page) {
        if (this.pageNumber !== page) {
          this.pageNumber = page;
          this._changePage();

          pageChangedEvent = new CustomEvent("pageChanged", {
            detail: {
              pageNumber: this.pageNumber
            },
            bubbles: true,
            cancelable: false
          });
          this.dispatchEvent(pageChangedEvent);
        }
      }
      return this.pageNumber;
    };

    Proto._readAttributes = function() {
      var pageStr, pageCountStr;
      pageStr = this.getAttribute("page");
      if (!pageStr) {
        pageStr = "0";
      }
      this.pageNumber = parseInt(pageStr, 10);
      pageCountStr = this.getAttribute("page-count");
      if (!pageCountStr) {
        pageCountStr = "0";
      }
      this.pageCount = parseInt(pageCountStr, 10);
    };

    Proto._changePage = function() {
      root.querySelector("span").innerHTML =
        "page " + this.pageNumber + " of " + this.pageCount;
    };

    Proto._linkClick = function(args) {
      var gotoPage;
      if (args.target.innerText === "first") {
        gotoPage = 1;
      } else if (args.target.innerText === "previous") {
        gotoPage = this.pageNumber - 1;
        if (gotoPage < 1) {
          gotoPage = 1;
        }
      } else if (args.target.innerText === "next") {
        gotoPage = this.pageNumber + 1;
        if (gotoPage > this.pageCount) {
          gotoPage = this.pageCount;
        }
      } else if (args.target.innerText === "last") {
        gotoPage = this.pageCount;
      }
      this.page(gotoPage);
    };

    Proto._addEventListeners = function() {
      var links, i;
      links = root.querySelectorAll("a");
      if (links.length > 0) {
        for (i = 0; i < links.length; i = i + 1) {
          links[i].addEventListener("click", this._linkClick.bind(this), false);
        }
      }
    };

    document.registerElement("x-pager", { prototype: Proto });
  })();
</script>

Themes

We are going to finish our component by introducing themes. :host(theme) allows us to specify styles for different themes. Let’s change our styling to the following:

<style>
  :host div {
    padding: 6px;
    width: 270px;
  }
  :host(.light) div {
    background-color: rgb(241, 243, 241);
  }
  :host(.dark) div {
    background-color: rgb(59, 60, 59);
  }
  :host a {
    margin-right: 6px;
    text-decoration: none;
  }
  :host a:hover {
    text-decoration: underline;
  }
  :host(.light) a {
    color: blue;
  }
  :host(.dark) a {
    color: rgb(236, 236, 246);
  }
  :host span {
    margin-left: 20px;
  }
  :host(.light) span {
    color: rgb(189, 201, 189);
  }
  :host(.dark) span {
    color: rgb(149, 160, 149);
  }
</style>

The above specifies “light” and “dark” classes that are available in our pager component. So, if we change the pager tag in our consuming page to:

<x-pager page="1" page-count="5" class="dark"></x-pager>

We should get something like the following: pager component

So, that rounds off our component nicely. The full listing for our pager component is below and also available from x-pager source

<template>
	<style>
		:host div {
			padding: 6px;
			width: 270px;
		}
		:host(.light) div {
			background-color: rgb(241, 243, 241);
		}
		:host(.dark) div {
			background-color: rgb(59, 60, 59);
		}
		:host a {
			margin-right: 6px;
			text-decoration: none;
		}
		:host a:hover {
			text-decoration: underline;
		}
		:host(.light) a {
			color: blue;
		}
		:host(.dark) a {
			color: rgb(236, 236, 246);
		}
		:host span {
			margin-left: 20px;
		}
		:host(.light) span {
			color: rgb(189, 201, 189);
		}
		:host(.dark) span {
			color: rgb(149, 160, 149);
		}
	</style>
	<div>
		<a href='#' title='Go to the first page'>first</a>
		<a href='#' title='Go to the previous page'>previous</a>
		<a href='#' title='Go to the next page'>next</a>
		<a href='#' title='Go to the last page'>last</a>
		<span class='x-pager-text'>page x of y</span>
	</div>
</template>

<script>
	(function () {
		var Proto = Object.create(HTMLElement.prototype),
			importDoc = document.currentScript.ownerDocument,
			root,
			pageNumber = 1,
			pageCount = 0;

		Proto.createdCallback = function() {
			var template = importDoc.querySelector("template"),
				clone = document.importNode(template.content, true);
			root = this.createShadowRoot();
			root.appendChild(clone);

			this._readAttributes();
			this._changePage();
			this._addEventListeners();
		};

		Proto.attributeChangedCallback = function(attrName, oldVal, newVal) {
		    this._readAttributes();
		    if (attrName === "page" || attrName === "page-count") {
		    	this._changePage();
		    }
		};

		Proto.page = function (page) {
			var pageChangedEvent;
			if (page) {
				if (this.pageNumber !== page) {
					this.pageNumber = page;
					this._changePage();

					pageChangedEvent = new CustomEvent("pageChanged", {
						detail: {
							pageNumber: this.pageNumber
						},
						bubbles: true,
						cancelable: false
					});
					this.dispatchEvent(pageChangedEvent);
				}
			}
			return this.pageNumber;
		};

		Proto._readAttributes = function () {
			var pageStr,
				pageCountStr;
			pageStr = this.getAttribute("page");
			if (!pageStr) {
				pageStr = "0";
			}
			this.pageNumber = parseInt(pageStr, 10);
			pageCountStr = this.getAttribute("page-count");
			if (!pageCountStr) {
				pageCountStr = "0";
			}
			this.pageCount = parseInt(pageCountStr, 10);
		};

		Proto._changePage = function () {
			root.querySelector("span").innerHTML = "page " + this.pageNumber + " of " + this.pageCount;
		};

		Proto._linkClick = function (args) {
			var gotoPage;
			if (args.target.innerText === "first") {
				gotoPage = 1;
			} else if (args.target.innerText === "previous") {
				gotoPage = this.pageNumber - 1;
				if (gotoPage < 1) {
					gotoPage = 1;
				}
			} else if (args.target.innerText === "next") {
				gotoPage = this.pageNumber + 1;
				if (gotoPage > this.pageCount) {
					gotoPage = this.pageCount;
				}
			} else if (args.target.innerText === "last") {
				gotoPage = this.pageCount;
			}
			this.page(gotoPage);
		};

		Proto._addEventListeners = function () {
			var links,
				i;
			links = root.querySelectorAll("a");
			if (links.length > 0) {
				for (i = 0; i < links.length; i = i + 1) {
					links[i].addEventListener("click", this._linkClick.bind(this), false);
				}
			}
		};

		document.registerElement('x-pager', {prototype: Proto});
	})();
</script>

Want more content like this?

Subscribe to receive notifications on new blog posts and courses

Required
© Carl Rippon
Privacy Policy