Windex: pretty-up your localhost

3 Apr 2018 · by David DeSandro

Windex icons

As 90% of my job is making static websites, I am often looking at directory index pages. You know, those ancient pages with titles like Index of /projects/. Long ago, I swore off the ugly unstyled 90's default and chose a higher path.

Windex is a little library that creates styled directory index pages with Apache. Simply put, it allows you to use CSS on your localhost. Features include:

  • SVG icons, sized pixel-perfect for 24x24 & multiples thereof
  • View HTML files without extensions: project/page.html at project/page
  • Nice mobile view with big tap targets

Windex screenshot

View Windex demo on CodePen

Setting up a practical localhost on macOS

I like to use localhost so I can view my projects in ~/projects. This allows me to create static sites that I can easily view in the browser, without having to start up a server. For example, going to http://localhost/masonry/sandbox allows me to view ~/projects/masonry/sandbox.

Below are instructions to set that up on macOS. Sorry Windows users, you're on your own here. This will make a single user's folder viewable for all users. For separate users folders like localhost/~username, view these instructions.


Open /etc/apache2/httpd.conf in your text editor. Making changes to this file will require administrator access. Change the following lines (line numbers may vary in your file):

Line 169: Enable mod_rewrite. This enables .htaccess files to rewrite URLs.

LoadModule rewrite_module libexec/apache2/mod_rewrite.so

Lines 238-239: Change DocumentRoot and subsequent Directory to your desired directory. This sets localhost to point to the directory.

DocumentRoot "/Users/username/projects"
<Directory "/Users/username/projects">

Line 252: Within this <Directory> block, add Indexes to Options. This enables file index view.

    Options FollowSymLinks Multiviews Indexes

Line 260: Change AllowOverride value to All. This enables .htaccess files.

    AllowOverride All

That block should look like:

DocumentRoot "/Users/username/projects"
<Directory "/Users/username/projects">
    # Possible values for the Options directive...
    Options FollowSymLinks Multiviews Indexes
    MultiviewsMatch Any

    # AllowOverride controls what directives...
    AllowOverride All

    # Controls who can get stuff from this server.
    Require all granted
</Directory>

In Terminal, start or restart apachectl.

sudo apachectl restart

View http://localhost in a browser. You'll should see the index page for ~/projects. Without Windex, it's ugly, but it works. With Windex, it's pretty.

If you messed up httpd.conf, you can replace it with its original at /etc/apache2/original/httpd.conf.

Viewing on other devices on macOS

You can view this localhost on another device like a phone.

  1. Open System Preferences. Select Sharing.
  2. Change Computer name to something rad, like thunderclaw, if you haven't already.
  3. Enable File sharing.

Now, you can view http://thunderclaw.local on another device if you are on the same Wi-Fi.


Windex is the one tool I use every day. Originally created in 2010, it is one of my first open-source projects. Before this recent overhaul, I had been using that first version for years, which had iPhone v1 glossy buttons. Let's see if this version lasts me until 2026.

What is `this` in event listeners?

29 Mar 2018 · by David DeSandro

I was just asked:

Hello desandro! I'm a new developer.

elem.addEventListener( startEvent, this );

I wonder what is this means. I'm confused. [edited]

Confusing, indeed! this in JavaScript is difficult to learn, and especially tricky to understand when used in event listeners.

Let's take a step back and look at a simplified example. Here is a demo for a mouse-draggable element.

var dragElem = document.querySelector('.draggable');

var x = 0;
var y = 0;
var dragStartX, dragStartY, pointerDownX, pointerDownY;

dragElem.addEventListener( 'mousedown', function( event ) {
  // keep track of start positions
  dragStartX = x;
  dragStartY = y;
  pointerDownX = event.pageX;
  pointerDownY = event.pageY;
  // add move & up events
  window.addEventListener( 'mousemove', onmousemove );
  window.addEventListener( 'mouseup', onmouseup );
});

function onmousemove( event ) {
  // how much has moved
  var moveX = event.pageX - pointerDownX;
  var moveY = event.pageY - pointerDownY;
  // add movement to position
  x = dragStartX + moveX;
  y = dragStartY + moveY;
  // position element
  dragElem.style.left = x + 'px';
  dragElem.style.top = y + 'px';
}

function onmouseup() {
  // remove move & up events
  window.removeEventListener( 'mousemove', onmousemove );
  window.removeEventListener( 'mouseup', onmouseup );
}

See the Pen Draggable, single element by Dave DeSandro (@desandro) on CodePen.

To start dragging, I first add a mousedown event listener. When triggered, I then add listeners for mousemove and mouseup. In onmousemove is where I calculate and set the element's position. In onmouseup, I remove mousemove and mouseup listeners to stop dragging.

This works just fine for a single element. Metafizzy plugins are designed to handle multiple instances on the same page.

var dragElems = document.querySelectorAll('.draggable');
for ( var i=0; i < dragElems.length; i++ ) {
  var dragElem = dragElems[i];
  // now what?...
}

One way to approach this is to wrap up the draggable code into its own big function and call that for each element. But I like to use classes with prototype to handle multiple instances of the same behavior.

// Dragger class
function Dragger( element ) {
  this.element = element;
  this.x = 0;
  this.y = 0;
  // doesn't work!
  this.element.addEventListener( 'mousedown', this.onmousedown );
}

Dragger.prototype.onmousedown = function( event ) {
  this.dragStartX = this.x;
  this.dragStartY = this.y;
  this.pointerDownX = event.pageX;
  this.pointerDownY = event.pageY;
  // doesn't work, again!
  window.addEventListener( 'mousemove', this.onmousemove );
  window.addEventListener( 'mouseup', this.onmouseup );
};

Now I need to add an event listener within the Dragger class functions. But there is a problem. I want to use this to reference the instance of the Dragger class within the event handler functions. But functions added with addEventListener will reference the bound element as this, not the function or object.

dragElem.addEventListener( 'mousedown', function() {
  console.log( this );
  // => Element
});

handleEvent

One solution is to use a little-known feature of browser JavaScript, handleEvent. An object with an handleEvent method will be triggered with when added with addEventListener. Within the handleEvent method, this will refer to the listener object, not the element.

var listener = {
  greeting: 'Hello ',
  handleEvent: function( event ) {
    console.log( this.greeting + event.type );
  },
};

dragElem.addEventListener( 'mousedown', listener );
// on mousedown...
// => 'Hello mousedown'

See simple handleEvent demo on CodePen.

The handleEvent method can be used for multiple events. You can specify logic by using event.type.

var listener = {
  greeting: 'Hello ',
  handleEvent: function( event ) {
    console.log( this.greeting + event.type );
  },
};

dragElem.addEventListener( 'mousedown', listener );
dragElem.addEventListener( 'mousemove', listener );
dragElem.addEventListener( 'mouseup', listener );
// on mousedown => 'Hello mousedown'
// on mousemove => 'Hello mousemove'
// on mouseup => 'Hello mouseup'

Back to the Dragger class. So now I add the handleEvent method to cooridate which event method to trigger. Then I can add this as the event listener.

// Dragger class
function Dragger( element ) {
  this.element = element;
  this.x = 0;
  this.y = 0;
  // add this as event listener, trigger handleEvent
  this.element.addEventListener( 'mousedown', this );
}

// trigger .ontype from event.type
// i.e. trigger onmousedown() from mousedown
Dragger.prototype.handleEvent = function( event ) {
  var method = 'on' + event.type;
  // call method if there
  if ( this[ method ] ) {
    this[ method ]( event );
  }
};

Dragger.prototype.onmousedown = function( event ) {
  this.dragStartX = this.x;
  this.dragStartY = this.y;
  this.pointerDownX = event.pageX;
  this.pointerDownY = event.pageY;
  // add this as event listener, trigger handleEvent
  window.addEventListener( 'mousemove', this );
  window.addEventListener( 'mouseup', this );
};

See the Pen Draggable event listeners, handleEvent by Dave DeSandro (@desandro) on CodePen.

So to the original question: this can be used as an event listener as it has a handleEvent method. That method then triggers other methods that match event.type, like onmousedown.

I learned the handleEvent technique back in 2010. I use it in all the Metafizzy plugins. But JavaScript has come a long way in that time.

bind this

Instead of adding circuitry through handleEvent, you can specify this with .bind().

The Function.prototype.bind() method lets you specify the value that should be used as this for all calls to a given function.

Using bind() has an additional benefit in that you can add multiple event listeners for the same event name.

this.element.addEventListener( 'click',
  this.onElementClick.bind( this ) );

this.button.addEventListener( 'click',
  this.onButtonClick.bind( this ) );

But, because bind() returns a new function, you will need to keep track of that function if you want to remove it later.

this.handleElementClick = this.onElementClick.bind( this );
this.handleButtonClick = this.onButtonClick.bind( this );
// add event listener
this.element.addEventListener( 'click', this.handleElementClick );
this.button.addEventListener( 'click', this.handleButtonClick );
// remove event listener
this.element.removeEventListener( 'click', this.handleElementClick );
this.button.removeEventListener( 'click', this.handleButtonClick );

Here's what the Dragger class looks like using bind().

function Dragger( element ) {
  this.element = element;
  this.x = 0;
  this.y = 0;
  // bind self for event handlers
  this.mousedownHandler = this.onmousedown.bind( this );
  this.mousemoveHandler = this.onmousemove.bind( this );
  this.mouseupHandler = this.onmouseup.bind( this );

  this.element.addEventListener( 'mousedown', this.mousedownHandler );
}

Dragger.prototype.onmousedown = function( event ) {
  this.dragStartX = this.x;
  this.dragStartY = this.y;
  this.pointerDownX = event.pageX;
  this.pointerDownY = event.pageY;

  window.addEventListener( 'mousemove', this.mousemoveHandler );
  window.addEventListener( 'mouseup', this.mouseupHandler );
};

See the Pen Draggable bind this by Dave DeSandro (@desandro) on CodePen.

Arrow functions

With the new ES6 hotness, you can specify this using arrow functions. Within an arrow function, this will refer to the enclosing object.

function Dragger( element ) {
  this.element = element;
  this.x = 0;
  this.y = 0;

  // event listeners, with arrow functions
  this.onmousedown = ( event ) => {
    this.dragStartX = this.x;
    this.dragStartY = this.y;
    this.pointerDownX = event.pageX;
    this.pointerDownY = event.pageY;

    window.addEventListener( 'mousemove', this.onmousemove );
    window.addEventListener( 'mouseup', this.onmouseup );
  };

  this.onmousemove = ( event ) => {
    var moveX = event.pageX - this.pointerDownX;
    var moveY = event.pageY - this.pointerDownY;
    this.x = this.dragStartX + moveX;
    this.y = this.dragStartY + moveY;
    this.element.style.left = this.x + 'px';
    this.element.style.top = this.y + 'px';
  };

  this.onmouseup = () => {
    window.removeEventListener( 'mousemove', this.onmousemove );
    window.removeEventListener( 'mouseup', this.onmouseup );
  };

  // add event listener
  this.element.addEventListener( 'mousedown', this.onmousedown );
}

See the Pen Draggable event listeners, arrow functions by Dave DeSandro (@desandro) on CodePen.

Personally, I'm not a fan of the arrow function technique for this scenario. It puts the method code inside another function, rather than outside on prototype. But I'm including it for completeness.

Each technique has its own pros and cons. handleEvent has served me well over the years, but I'm finding that I run into event name conflicts with big plugins like Flickity. So I'm starting to use out bind() a bit more. But then I miss the elegance of adding just this and not having to deal with extra event handler functions. Arrow functions, meanwhile, are just not for me.

Flickity v2.1: fullscreen, hash, & more

23 Mar 2018 · by David DeSandro

Flickity

Happy birthday Flickity! 3 years old! Flickity has grown to be Metafizzy's second most popular library. As such, it deserved some new goodies in a big new release, v2.1.

Fullscreen

View Flickity fullscreen by enabling fullscreen. Click the fullscreen button to resize the carousel to take up the entire browser window. Now you don't need a separate library to zoom images. ENHANCE.

Hit ESC to exit fullscreen. Flickity fullscreen comes with its own API with viewFullscreen and exitFullscreen methods.

This feature was a long time coming. It was first requested while Flickity was still in beta. The issue gained close to a hundred 👍 over the years. I'm delighted to finally ship it.

URL hash

Select slides with URLs with hash. This works both ways: clicking on hash links like <a href="#carousel-cell2"> will select matching the #carousel-cell2 element, and selecting a new slide will change the page URL to the selected cell's id. So you can share URLs to specific slides.

Both fullscreen and hash are add-on features. They are maintained as separate projects outside of Flickity, so the Flickity source code doesn't get bloated with new features. You can choose to add these features by adding their scripts to your project. See flickity-fullscreen and flickity-hash on GitHub.

lazyLoad srcset

Lazy-load images with srcset by setting the data-flickity-lazyload-srcset attribute. You can also set data-flickity-lazyload-src as well to set src as a fallback.

<img data-flickity-lazyload-srcset="
    walrus-large.jpg 720w,
    walrus-med.jpg 360w"
  sizes="(min-width: 1024px) 720px, 360px"
  data-flickity-lazyload-src="walrus-large.jpg"
  />

change event

Flickity's select event is triggered any time a slide is selected. But it will trigger even if the same slide is selected. Flickity v2.1 now has the change event, which will only be triggered when the selected slide has changed.

$carousel.on( 'change.flickity', function( event, index ) {
  console.log( 'Slide changed to ' + index )
});

And more!

  • Added draggable: '>1' option setting (now default) to disable dragging if only one slide.
  • Added ready event. Added on option to capture initial events.
  • Enabled staticClick event when not draggable.
  • Bug fixes with prepend, remove, and wrapAround.

Read the v2.1.0 release notes for details.

With these features, Flickity is now perfect... Naw. You can continue to help me select new features by looking over requested features on the GitHub issue tracker and adding your 👍 reaction.

New words

26 Feb 2018 · by David DeSandro

Hoefler & Co. Isotope

Last month, Hoefler & Co. released a new family of typefaces named Isotope. The name immediately grabbed my attention.

Early on, I chose common words for projects names as was the fashion, like Masonry and Isotope. But since, I have switched for the unique: Packery, Flickity, Huebee. Unique names offer distinct advantages:

  1. Easier tracking for SEO & social media mentions
  2. Better availability with domains, npm, GitHub

Developers are notoriously bad at naming things. How many Papers, Atlases, and Fabrics are out there? Adopting generic names leaves you wide open for collision.

We are creating new things in a new era. What better place for new words? Go on, get weird, and make a new name for yourself.

CodePen showcase: Round 2

14 Feb 2018 · by David DeSandro

It's time for another round-up of some of the finest demos made with Metafizzy plugins.

I'll be collecting these in the Metafizzy showcase CodePen collection, as well as individual collections for each plugin.


Gabrielle Wee got my attention with pens that combined elegant design with fun animations. I was thrilled to see her work with Metafizzy.

Isotope Card Match is a perfect example. This demo makes use of a little known Isotope shuffle method to give a simple memory game a kick. The design is stunning: all done with CSS. I love how the card back's design is a timer.

See the Pen Isotope Card Match Game by Gabrielle Wee ✨ (@gabriellewee) on CodePen.

Gabrielle followed up with the Pokémon Sprite Sorter. It displays sprites for 184 Pokémon. This demo nicely shows Isotope's ability to organize information. You can make sense of a huge list of Pokémon by filtering via type or game release. Nice touches include pixel art sprites and background colors to indicate types.

See the Pen Pokémon Sprite Sorter by Gabrielle Wee ✨ (@gabriellewee) on CodePen.


Matt Soria put together this bespoke Font Showcase with Flickity. I like how Flickity is just part of the experience, allowing you to page through the characters.

See the Pen Font Showcase with Flickity by Matt Soria (@poopsplat) on CodePen.


Jack Rugile is a CodePen star. He produces wild visuals, fluid animations, and dang ol' video games in JavaScript. He was kind enough to descend from his heavenly realm and toil with Flickity to output Carousel Lock Interface. It's got it all: slick visuals in CSS, glitchy sounds, and an outside-the-box concept for Flickity. Check how the LOCKED status blinks.

See the Pen Carousel Lock Interface by Jack Rugile (@jackrugile) on CodePen.


I'm delighted to see what code artists can do when they get their hands on a Metafizzy plugin. If you're interested in making a demo, get in touch so I can make it worth your while. I'd love to show your work in the next round-up!

Logo Pizza vol. 2 delivered

24 Jan 2018 · by David DeSandro

Logo Pizza vol. 2

Logo Pizza vol. 2 is out! Logo Pizza is a collection of 50 logos, ready for sale. Volume 2 includes 33 all new designs.

Last year's initial release of Logo Pizza was a surprise success. Looking to capitalize on it, I spent my autumn days designing a new set of logos.

Results

Volume 2 was released last November 28th. In the two subsequent months, it has sold well, but nowhere near the gangbusters experienced in the first volume.

The good news: there are some great logos available for sale at an incredible price.

Logo Pizza vol. 2

Logo Pizza Osprey

The bad news: I've got too much inventory sitting in the warehouse, so-to-speak.

It's always difficult to explain why a project succeeds or fails. But, if I had to make a guess, I'd say the same attribute that made the first volume so successful was the un-doing of the second. And that attribute is: novelty.

The original Logo Pizza was, ahem, entirely original. A new concept, with fresh designs, with a gimmicky pricing mechanism. My thinking was the the second volume would be able build upon the first volume's appeal. But maybe its actual appeal was just in its newness. By the time volume 2 rolled around, the thrill was gone. Logo Pizza vol. was news-worthy. Volume 2 was old news.

As I grow older in my profession and I release more projects, I'm starting to see how much luck is involved in the success or failure of projects. Maybe volume 1 was lucky and volume 2 was not.


Sales are still steady coming in. If you have had any interest in a logo, buy soon before someone else does. There are some straight bangers that somehow have not been snatched up yet. Blue jay, Mechanical hare, Dockyarder, and more are just waiting to be procured. Don't sleep on these!