# Thumbslider

## What is this?

A responsive javascript carousel with thumbnails.

### Features

- Creates thumbnails from slide content
- Carousel structure is responsive and scalable within container dimensions
- Plain Javascript - no reliance on any framework (animation is pluggable)

## Fantastic! How do I get it?

### npm

```
npm install thumbslider --save
```

### bower

```
bower install thumbslider --save
```

## What's the Setup?

Add _thumbslider_ resources to your html `<head>`.

```html
<link href="../dist/thumbslider.min.css" rel="stylesheet" />

<script src=".../dist/thumbslider.min.js"></script>
```

Polyfills are available also (See _Polyfilling_ section).

Next, you need to have a container element with some content

```html
<div id="carousel_container" class="ts-theme-default">
  ...
</div>
```

Make sure the container is styled to have a width and height.

Finally, to create and render the carousel (with default options)

```js
var carousel = thumbslider.create('carousel_container');
carousel.start(); // start rotation
```

Animation can be plugged into _thumbslider_.  

For instance, if you have JQuery in your page, you can create the carousel like so:

```js
var carousel = thumbslider.create('carousel_container', { animator: thumbslider.tweeners.jquery });
```

## How Does It All Work?

To start, here's some example content:

```html
<div id="carousel_container" class="ts-theme-default">
  <div>
    <h1>
      <span data-ts-thumb>Top Header</span>
    </h1>
    <p>Paragraph Content</p>
  </div>
  <div data-ts-name="sub-topic">
    <h2>Sub Header</h2>
  </div>
  More Content Here!
  <img src="http://www.server.com/path/to/some/image.png" />
</div>
```

_thumbslider_ assumes that every top-level non-text child node of the container is a content item.

In the above example content, the content items would be the two top level `<div>` elements and the `<img>` element.

Each content item is given an identifier in the carousel.  This name comes from its 'data-ts-name' attribute.

```html
<div data-ts-name="sub-topic">
  ...
</div>
```
If no attribute is specified a random identifier is generated.

A thumbnail node for the content item is derived from the content itself.  This node will be selected as

1. the first descendant of the content item found having a 'data-ts-thumb' attribute, or
2. the first non-text node in the content item, or
3. the content item itself

So, in the above example

- the first `<div>` content item uses the `<span>` as its thumbnail item
- the second `<div>` will have it's `<h2>` element as its thumbnail
- and the `<img>` content item will also double as the thumbnail itself

The content item is always cloned and copied to the selector as a thumb element.  You can instead have the thumb element moved from its current location by specifying 'remove' as the value of 'data-ts-thumb'.

```html
<div>
  <h1>
    <span data-ts-thumb="remove">Top Header</span>
  </h1>
  ...
</div>
```

Here's a quick ref for the content attributes.

- **_data-ts-name_** (optional: for top-level container child)  
Gives the content item a name.  A random name is generated if not specified.

- **_data-ts-thumb_** (optional: for any descendant of a content item)  
Identifies element to be used as thumbnail for the content item.  Specify 'remove' as value for attribute to prevent cloning.  The first non-text node in the content item or the content item itself is used if not specified.

### Configuration Options

```js
var config = { ... }
thumbslider.create('my_carousel_id', config);
```

| option | type | default | description |
| ------ | :--: | :-----: | ----------- |
| activeClass* | _string_ | 'active' | CSS class name to add to the current content item |
| animator | _function_ | (default non-animating function) | Function for animation (See _Animators_ section) |
| icons* | _Object_ | (css triangles) | Class names for the nav icons (See _Icon Controls_ section) |
| pauseOnMouseover* | _boolean_ | true | Pause carousel rotation on mouseover? |
| responsive | _object_ | { xs: 480, sm: 768, md: 1024, lg: 1200 } | Responsive labels and thresholds (See _Configuring Responsively_ section) |
| rotateDuration* | _number_ | 0.8 | Seconds of carousel rotation animation |
| rotateEasing* | _any_ | null | Library-specific easing value for carousel rotation |
| rotateInterval* | _number_ | 3 | Seconds between each rotation |
| rotateIntermission* | _number_ | 3 | Seconds to wait before restarting rotation after user interaction |
| selectorDisplay* | _string_ | 'visible' | Display as 'hidden', 'visible', 'transient', or 'overlay' (See _Selector Display_ section) |
| selectorHideDelay* | _number_ | 0 | number of seconds before hiding a displayed 'transient' selector.  Set to `0` to disable |
| selectorPosition* | _string_ | 'bottom' | Position selector at 'top', 'bottom', 'left', or 'right' |
| selectorSize* | _number_ | 0.2 | Size of selector (either height or width dependent upon **selectorPosition**) as percentage of container |
| showIconsOnHover* | _boolean_ | true | Show controls on carousel mouse hovers? (Controls always shown for `false`) |
| showSelector* | _boolean_ | true | Display 'transient' selector initially? |
| showThumbContent* | _boolean_ | true | Display content for thumbnails? |
| shuffle* | _boolean_ | false | Shuffle the carousel content items? |
| slideScaleType* | _object_ | { img: 'content-max', def: 'none' } | Slide scaling by tag or class name (See _Content Scaling_ section) |
| slideTransition* | _function_ | (default 'slide' function) | Content slide transition animation (See _Slide Transitions_ section) |
| startAt* | _string_ or _number_ | (first content item) | Content item name or index to display when carousel loads |
| thumbScaleType* | _object_ | { img: 'content-max', def: 'viewer' } | Thumb scaling by tag or class name (See _Content Scaling_ section) |
| thumbSize* | _number_ | 1.0 | ratio of secondary to primary dimension (at `1.0` carousel tries to maintain square-shaped thumbs) |
| toggleDuration* | _number_ | 0.8 | Seconds of 'transient' selector toggle |
| toggleEasing* | _any_ | null | Library-specific easing value for 'transient' selector toggle |
| viewerOrientation* | _string_ | 'horizontal' | Direction of viewer scroll ('vertical' or 'horizontal') |

_\* = option can be configured responsively_

#### Animators

JQuery

```js
{ animator: thumbslider.tweeners.jquery }
```

GreenSock (requires TweenMax and ScrollToPlugin)

```js
{ animator: thumbslider.tweeners.gsap }
```

No animation (the default)

```js
{ animator: thumbslider.tweeners.none }
```

Custom

```js
{ animator: function(element, time, params) { ... } }
```

**Animator Function Parameters**

| param | type | description |
| ----- | ---- | ----------- |
| element | _object_ | DOM Element to be animated |
| time | _number_ | Animation duration in seconds |
| params | _object_ | Additional animation parameters |
| params.after | _function_ | function to execute when/after animation completes |
| params.attrs | _object_ | element attributes to be animated |
| params.before | _function_ | function to execute when/before animation begins |
| params.during | _function_ | function to execute for each frame of animation |
| params.ease | _any_ | easing value for animation |
| params.styles | _object_ | element styles to be animated |

Note that individual animation requests will generally include only a subset of `params`.

#### Icon Controls

There are four possible values for `config.icons`, and the defaults are

```js
{
    icons:
    {
        top: 'deficon',
        left: 'deficon',
        bottom: 'deficon',
        right: 'deficon'
    }
}
```

The class name values are added directly to the `<i>` tag for the control.

```html
<i class="deficon"></i>
```

The default 'deficon' class simply makes black css triangles for the nav controls.

To use actual icons you can use your favorite icon provider.

For instance, here is an example using [Font Awesome](http://fontawesome.io/).

```js
{
    icons:
    {
        top: 'fa fa-chevron-up',
        left: 'fa fa-chevron-left',
        bottom: 'fa fa-chevron-down',
        right: 'fa fa-chevron-right',
    }
}
```

This uses the library's 'chevron' icons for nav controls.  Don't forget to properly setup your icon provider in your html.

#### Selector Display

There are four possible values for `config.selectorDisplay`.

| value | viewable | offsets viewer | details |
| ----- | :------: | :------------: | ------- |
| _hidden_ | never | no | Selector is hidden from view and not updated |
| _visible_ | always | yes | Selector is part of content pane |
| _transient_ | toggle | no | User can toggle selector display |
| _overlay_ | always | no | Similar to 'transient' but without toggle |

Note that the following config options have no effect unless selector display is 'transient'.

- selectorHideDelay
- showSelector
- toggleDuration
- toggleEasing

#### Content Scaling

HTML content can be scaled for either the slide or the thumbnail, and this scaling is specified by element tagname or css classname.

For instance, to scale all image content items to completely fill the thumbnail,

```js
{ thumbScaleType: { img: 'content-max' } }
```

Or, to scale content items with a 'scale-me' class to fit in the slide,

```js
{ slideScaleType: { 'scale-me': 'content-min' } }
```

There are four possible values for `config.slideScaleType` and `config.thumbScaleType`.

- _none_  
No content scaling is performed.

- _content-max_  
The content element is scaled up or down so that its smallest dimension fits in the slide or thumbnail.  The content is then centered for the opposite dimension.

- _content-min_  
The content element is scaled up or down so that its largest dimension fits in the slide or thumbnail.  The content is then centered for the opposite dimension.

- _viewer_  
The content container (**ts-slide-content** or **ts-thumb-content**) has its size increased to the size of the viewer area to allow content to flow and then (css transform) scaled back down by 50%.  This is obviously more useful for thumbnails.

#### Slide Transitions

You can specify the transition for slide rotation in the configuration.

```js
{ slideTransition: thumbslider.transitions.xxx }
```

Replace 'xxx' in the above with one of the following:

- **fade**  
Fades in the next slide while fading out the current one.

- **slide**  
Slides in the next slide while sliding out the current one.

- **stack**  
Slides the next slide onto the current one.

- **unstack**  
Slides the current slide off to reveal the next one.

#### Configuring Responsively

The default responsive setup is

```js
{ responsive: { xs: 480, sm: 768, md: 1024, lg: 1200 } }
```

Use responsive labels ('xs', 'sm', etc.) inside other config options to have those values change responsively.

```js
{
    selectorDisplay: { xs: 'hidden', def: 'transient' },
    selectorSize: { sm: 0.2, lg: 0.24, def: 0.3 }
}
```

Remember that/to

- responsiveness is based on carousel container width, not window width.
- the responsive label whose value is greater than or equal to the current container width will be in effect.
- always specify a default (`def`) value in a config option to catch width values beyond the defined responsive configuration.
- if a config option does not specify a value for the responsive label currently in effect the next level label (or `def`) will be used.

### Generated Carousel HTML

```html
<div class="ts-carousel">
  <div class="ts-viewer">
    <div class="ts-control ts-prev">
      <i class=""></i>
    </div>
    <div class="ts-view">
      <div class="ts-slides">
        <div class="ts-slide" data-ts-name="[CONTENT_NAME]">
          <div class="ts-slide-content">
            < content element >
          </div>
        </div>
        ... (repeated slide elements)
      </div>
    </div>
    <div class="ts-control ts-next">
      <i class=""></i>
    </div>
  </div>
  <div class="ts-selector">
    <div class="ts-select">
      <div class="ts-thumbs">
        <div class="ts-focus"></div>
        <div class="ts-thumb" data-ts-name="[CONTENT_NAME]">
          <div class="ts-thumb-overlay">
            <div class="ts-thumb-content">
              < content element >
            </div>
          </div>
        </div>
        ... (repeated thumb elements)
      </div>
    </div>
    <div class="ts-toggle"></div>
  </div>
</div>
```

Note that above HTML will replace original container contents.

### Contextually Applied CSS Classes

- For **'ts-content'** element:

    - `'ts-selector-xxx'`  
    Applied with 'xxx' as the current `config.selectorDisplay` value

    - `'ts-selector-xxx'`  
    Applied with 'xxx' as the current `config.selectorPosition` value. This is not applied if `config.selectorDisplay` is 'hidden'.

    - `'ts-selector-xxx'`  
    Applied with 'xxx' as the current orientation of the selector ('horizontal' or 'vertical'). This is not applied if `config.selectorDisplay` is 'hidden'.

- For **'ts-slide'** and **'ts-thumb'** elements:

    - `config.activeClass`  
    Applied as these content items become current via selection or rotation.

### Theming/Styling Guidelines

Remember that the carousel container must have height and width set.

When applying CSS styles to the generated HTML elements know that

| properties |  |
| ---------- | --- |
| top, left, bottom, right, position | should not be changed |
| z-index, overflow | can be set as necessary but not recommended |
| height, width | are ignored (values calculated and set inline) |

In general, you should use pixel measurements (px) for styling your carousel.

There are a few themes available in **/dist/themes** that you can use outright or as a starting point for your own theme.

### Thumbslider API

#### Module Functions

The following functions are available on the _thumbslider_ module.

- `create(name:string, options:object):Carousel`  
Creates a new carousel instance.

- `get(name:string):Carousel`  
Returns a given carousel instance or `null` if not found.

- `names():array`  
Returns a list of all carousel names.

- `options(name:string):object`  
Returns a copy of the configuration options used to create a given carousel instance or `null` if not found.

- `update(name:string, options:object):Carousel`  
Resets the given carousel with the specified configuration changes (i.e., these changes are merged with the carousel's original configuration)

#### Carousel Interface

There are a few methods that can be called on a _thumbslider_ carousel instance.

- `currentIndex():number`  
Returns the index of the current content item.

- `getName():string`  
Returns the name (container id) of the carousel instance.

- `getSlide(id:string|number):node`  
Returns the DOM element for the content slide by index or name.

- `getThumb(id:string|number):node`  
Returns the DOM element for the content thumbnail by index or name.

- `itemCount():number`  
The number of content items in the carousel.

- `remodel():this`  
Recalculates the display of the carousel.  Call this if you make style or other display changes to the carousel.

- `start():this`  
Starts carousel rotation.

- `stop():this`  
Stops carousel rotation.

Also see _Carousel Events_ below for event registration methods available on the carousel interface.

#### Carousel Events

_thumbslider_ uses [event-mixer](https://www.npmjs.com/package/event-mixer) for event emission.  

Event registration methods are mixed into the carousel interface using the below event names.

| event name | data params | default preventable? | details |
| ---------- | ----------- | :------------------: | ------- |
| rotate | `nextIndex:number` | yes | occurs just before the next content item is displayed |
| toggle | `shown:boolean` `willShow:boolean` | yes | occurs just before a transient selector is hidden/shown.  This can be fired even if `shown == willShow` |

## Anything Else I Should Know?

### Polyfilling

The polyfill-candidate language features needed by _thumbslider_ include

```
    Array.isArray
    Array.prototype.filter
    Array.prototype.find
    Array.prototype.forEach
    Array.prototype.indexOf
    Array.prototype.map
    Array.prototype.reduce
    document.querySelector
    Element.prototype.classList
    getComputedStyle
    Object.assign
    Object.keys
    Symbol
```

You can use these by including 'thumbslider.poly.js' rather than 'thumbslider.js'.

```html
<script type="text/javascript" src="../dist/thumbslider.poly.min.js"></script>
```

This makes the `thumbslider` export a function.  Pass this function a callback that will get executed once the polyfills are loaded.

```js
thumbslider(function(ts)
{
    // Create carousel instances here
    ts.create( ... );
});
```

The first call to this function adds script tag to `<head>` that grabs the necessary polyfills from the [Polyfill.io](https://polyfill.io/v2/docs/) CDN.  Subsequent calls are queued or executed immediately if the features are already loaded.

### Links

{ [distrib](https://bitbucket.org/captison/thumbslider/src/master/dist) }
{ [updates](https://bitbucket.org/captison/thumbslider/src/master/CHANGELOG.md) }
{ [feedback](https://bitbucket.org/captison/thumbslider/issues) }
{ [license](https://bitbucket.org/captison/thumbslider/src/master/LICENSE) }
{ [versioning](http://semver.org/) }

Please be sure to check 'updates' link when upgrading to a new version.

### Examples

There are a few _thumbslider_ examples in the **/examples** folder.  These will be posted online somewhere soon.

### Tests

_thumbslider_ uses [Jasmine](https://jasmine.github.io) for testing and [Istanbul](https://www.npmjs.com/package/istanbul) for coverage analysis.

Test coverage is imcomplete at this time.

```
npm test
```

In browsers _thumbslider_ works as intended in the latest versions of Firefox, Chrome, Safari, and Opera.  It works well in Edge also.  There are some issues with IE for now.

### Finally

Happy Carouselling!
