wearefine/frob-core

View on GitHub
README.md

Summary

Maintainability
Test Coverage
# Frob Core

[![Build Status](https://travis-ci.org/wearefine/frob-core.svg?branch=master)](https://travis-ci.org/wearefine/frob-core) [![Code Climate](https://codeclimate.com/github/wearefine/frob-core/badges/gpa.svg)](https://codeclimate.com/github/wearefine/frob-core)

Because you're not [hardcore](https://www.youtube.com/watch?v=f-mPnmfrm6I).

P.S. The inline code documentation in this pup is rock solid.

## JavaScript

1. Copy `source/javascripts/frob_core_helpers.js` and `source/javascripts/frob_core.js` to your project.
1. Apply the structure of `source/javascripts/application.js` to your project.
1. Start Frob Core (usually in `application.js`): `;(function() { window.FCH = new FrobCoreHelpers(FC); })();`

### Initialization and Options

The core holder must be passed in as the first argument for initialization, i.e. `new FrobCoreHelpers(FC)`, but all other options are, fittingly, optional. These are passed in via object as the second argument, i.e. `new FrobCoreHelpers(FC, { ... })`.

| Option | Type | Default | Description
|---|---|---|---|
| `mobile_fps` | boolean | true | Attach the scroll listener for `u-disable-hover` |
| `breakpoints` | function | null | To set custom breakpoint, pass a function with two args and return a `string: boolean` object (example below) |
| `preserve_breakpoints` | boolean | true | Merge custom breakpoints with [default breakpoints](#breakpoints). If false, value of `breakpoints` removes all default breakpoints |

```javascript
window.FCH = new FrobCoreHelpers({}, {
  breakpoints: function(ww) {
    return {
      boot_size: ww < 12
    };
  },
  preserve_breakpoints: false
});

// FCH.bp => { boot_size: <true|false> }
```

### Hooks

Direct descendants of the core holder can access hooks using reserved property names. These are called automagically, avoiding declaring multiple event listeners on the `document` or `window`.

```javascript
FC.ui = {
  ready: {
    if(FCH.exists('.js-slider')) {
      this.mySuperSliderInitialization();
    }
  },

  mySuperSliderInitialization: function() {
    ...
  }
};
```

For example, use the `resize` function to change the width of `.mydiv` to match the window's width.

```javascript
FC._ui = {
  resize: function() {
    $('.mydiv').width(FCH.dimensions.ww);
  }
};
```

Hooks can also be added by adding a function to the appropriate `FCH` array.

```javascript
var sticky = function($el) {
  $el.css('position', 'fixed');
}
FCH.scroll.push(sticky);
```

| Hook | Called on |
|---|---|
| `scroll` | window scroll |
| `resize` | window resize |
| `ready` | DOMContentLoaded |
| `load` | window onload |

**Special Note**: Using `this` in a hook function will refer to the parent namespace, not `FrobCoreHelper`.

```javascript
FC._ui = {
  boo: function() {
    alert('BOOooooOOoooOO');
  }

  resize: function() {
    this.boo();
  }
}
// a scary alert is triggered every time the window is resized
```

#### Debounced Events

Sometimes, a callback function on rapid-firing window events can be resource-intensive. Thanks to pioneering efforts from [John Hann](http://unscriptable.com/2009/03/20/debouncing-javascript-methods/), [Paul Irish](http://www.paulirish.com/2009/throttled-smartresize-jquery-event-handler/), and [Underscore.js](https://github.com/jashkenas/underscore/blob/master/underscore.js#L853), these events can be tricked to fire only once or twice instead of continuously.

Wrap your function in `FCH.debounce` when adding it to a hook.

```javascript
FC.nav_listeners = {
  ready: function() {
    FCH.resize.push( FCH.debounce( this.resourceConsumingFunction.bind(this) ) );
  },

  resourceConsumingFunction: function() {
    this.destroy();
  },

  destroy: function() { ... }
};
```

| Param | Description |
|---|---|
| `func` | Callback function |
| `threshold` | Milliseconds to check between fires (defaults to 250) |

### Breakpoints

These defaults are available from `FCH.bp`. Returns boolean. They can be appended, overwritten, or removed entirely when declaring options.

| Accessor | Description |
|---|---|
| small | window width less than or equal to 767 |
| small_up | window width greater than or equal to 768 |
| medium_portrait | window width between 768 and 960 |
| medium | window width between 768 and 1024 |
| large_down | window width less than 1024 |
| large | window width greater than 1024 |

```javascript
if(FCH.bp.small) {
  FCH.$body.addClass('mobile');
}
```

### Dimensions

Available from `FCH.dimensions`

| Accessor | Description |
|---|---|
| ww | window width |
| wh | window height |

### Cached jQuery Objects

Only available if jQuery is included.

| Accessor | Description |
|---|---|
| `FCH.$body` | equivalent to `$('body')` |
| `FCH.$window` | equivalent to `$(window)` |

### Default Values

Add default values for missing values in functions, similar to Ruby's `variable ||= value`

```javascript
function(required_value, missing_value) {
  missing_value = FCH.setDefault(missing_value, 10);
}
```

### addClass

`addClass` adds a class to an element with vanilla JS.

| Param | Description |
|---|---|
| `element` | JS Object |
| `class` | String of class to be added |

```javascript
var el = document.createElement('div');
FCH.addClass(el, 'foo'); // el.className is now "foo"
```

### removeClass

`removeClass` removes a class to an element with vanilla JS if the element already has that class.

| Param | Description |
|---|---|
| `element` | JS Object |
| `class` | String of class to be removed |

```javascript
var el = document.createElement('div');
el.className = 'foo';
FCH.removeClass(el, 'foo'); // el.className is now ""
```

### toggleClass

`toggleClass` adds a class to an element with vanilla JS if the element does not already have the class, removes the class if it does.

| Param | Description |
|---|---|
| `element` | JS Object |
| `class` | String of class to be toggled |

```javascript
var el = document.createElement('div');
FCH.toggleClass(el, 'foo'); // el.className is now "foo"
FCH.toggleClass(el, 'foo'); // el.className is now ""
```

### Loops

`loop` saves characters on `for` loops.

| Param | Description |
|---|---|
| `selector` | Array, NodeList or DOM selector |
| `callback` | requires one argument - the current looped item in callback |

```javascript
var titles = '';
FCH.loop('.posts', function(post) {
  titles += post.querySelector('h2').textContent;
});
```

### Local Storage

Read, Update, and Destroy localStorage objects.

| Function | Description |
|---|---|
| `FCH.localGet(key)` | retrieve value (returns false if key is not defined) |
| `FCH.localSet(key, value)` | create or update key |
| `FCH.localClear({optional} key)` | if key is provided, destroy just that key; otherwise destroy all keys |

### Existence

Check to see if an element exists on the page

```javascript
// <div class="henry"></div>

FCH.exists('.henry') // => true
```

### IE

Test for IE versions. Returns boolean. **Only use in the most trying of times. Agent strings are unreliable.**

| Accessor | Description |
|---|---|
| `FCH.IE10` | true if browser is IE10 |
| `FCH.IE9` | true if browser is IE9 |
| `FCH.anyIE` | true if browser is Internet Explorer |

## SCSS

1. Copy `source/stylesheets` to your project (usually replacing `app/assets/stylesheets`)

### Variables

| Variable | Description |
|---|---|
| `$em-base` | font-size (must be in pixels) used for `emCalc` and `remCalc`. |
| `$f-sans` | font stack for sans-serif fonts |
| `$f-serif` | font stack for serif fonts |
| `$f-script` | font stack for script fonts |
| `$f-monospace` | font stack for monospaced fonts |
| `$f-international` | font fallback stack for multilingual fonts |
| `$c-white` | vanilla palette for `#fff` |
| `$c-black` | vanilla palette for `#000` |

Font-stack variables are designed to be applied after custom font names.

```scss
$f-opensans: 'Open Sans', $f-sans;
$f-content: $f-opensans;
```

### Mixins

#### hover

Quick shortcut for `&:hover { ... }`. Accepts color value or block.

```scss
@include hover($c-white)

@include hover {
  background: $c-white;
}
```

#### stack

Make `z-index`ing easy. Create an ordered list of your classes and call it inside your class.

```scss
$lightbox: (
  'lightbox-bg',
  'lightbox-content'
);

.lightbox {
  &-bg {
    @include stack($lightbox); // z-index: 1
  }
  &-content {
    @include stack($lightbox); // z-index: 2
  }
}
```

If you want to name your ordered list different from your classes, pass a key as the second argument.

```scss
.lightbox {
  &-bg {
    @include stack($lightbox, 'lightbox-content'); // z-index: 2
  }
  &-content {
    @include stack($lightbox, 'lightbox-bg'); // z-index: 1
  }
}
```

#### bp


| Param | Accepts | Default |
|---|---|---|
| `$name` | string | required |
| `$breakpoint_list` | map | `$breakpoints` |
| `$additional_breakpoints` | arglist<string> | `null` |

Put a block of content within a media breakpoint using the `$breakpoints` list defined in `_variables.scss`.

```scss
.show--mobile {
  display: none;
  @include bp(small) {
    display: block;
  }
}
```

To use a custom set of breakpoints, pass the list in the second argument (default is `$breakpoints`). 

```scss
$tweakpoints: (
  'really-specific-nav-size': ( min-width: 438px )
);
@include bp(really-specific-nav-size, $tweakpoints) { ... }
```

Multiple breakpoints can be combined with an [arglist](https://www.sitepoint.com/sass-multiple-arguments-lists-or-arglist/) starting at the third argument. *Note that proper order should be obeyed and will not be automatically corrected (i.e. including `print` or `screen` as the third argument).*

```scss
.narrow-print {
  // Prints `@media only print and (min-width: 421px) and (max-width: 767px)`
  @include bp(print, $breakpoints, small, medium-down) { ... }
}
```

#### triangle

Apply a CSS triangle to an element.

| Param | Accepts | Default |
|---|---|---|
| `$direction` | top, bottom, left, right | top |
| `$color` | hex | `$c-black` |
| `$size` | px | `30px` |
| `$shadow` | boolean | `false` |

```scss
.nav-dropdown {
  @include triangle(bottom, $c-white);
}
```

#### placeholder

Pass a block to apply properties to input placeholder styles. Strongly recommended to be used only at root-level. Accepts block.

```scss
@include placeholder {
  color: $c-gray;
  text-shadow: 1px 1px 0 $c-black;
}
```

#### ie

Target specific Internet Explorer versions, and optionally pass in a version number. **Only use this mixin as a last resort. Feature detection is strongly preferred.** Accepts block.

```scss
.animated-element {
  @include ie { ... } // .ie .animated-element
  @include ie(9) { ... } // .ie9 .animated-element
}
```

#### touch

Target touch devices using [Modernizr](http://v3.modernizr.com/download/#-backgroundcliptext-canvas-canvastext-cssfilters-cssgradients-csspositionsticky-cssremunit-csstransforms-csstransforms3d-csstransitions-cssvhunit-flexbox-flexboxlegacy-flexboxtweener-inlinesvg-localstorage-picture-preserve3d-srcset-svgclippaths-svgfilters-touchevents-cssclasses-dontmin). Accepts block.

```scss
.nav {
  @include touch { ... } // .touch .nav
}
```

#### clearfix

Use a [Nicolas Gallagher](http://nicolasgallagher.com/micro-clearfix-hack/) clearfix. A `.clearfix` class is available in `_utilities.scss`

```scss
.cf {
  @include clearfix;
}
```

#### vertical-center

[Vertically align](http://zerosixthree.se/vertical-align-anything-with-just-3-lines-of-css/) an object without `vertical-align`.

```scss
.center-between {
  @include vertical-center;
}
```

### Functions

#### emCalc

Convert `px` to `em`. Relies on `$em-base` in `_fine_variables.scss`, but this can be overwritten in your own `_variables.scss`

```scss
body {
  font-size: emCalc(16px);
}
```

#### remCalc

Convert `px` to `rem`. Relies on `$em-base` in `_fine_variables.scss`, but this can be overwritten in your own `_variables.scss`

```scss
body {
  font-size: remCalc(16px);
}
```

#### match

Find pair of value or key in map. If value/key cannot be found, returns false

```scss
$descriptions: (
  'big' : 'blue',
  'wet' : 'wide'
);

.whats-the-sea-like {
  &:before {
    background: match($descriptions, 'big'); // 'blue'
    content: match($descriptions, 'wet'); // 'wide'
  }
}
```

#### negate

Invert a number.

```scss
.upside-down {
  top: negate(10px);
}
```

#### gray

Use a gray color from the `$grays` map.

```scss
$grays: (
  30: #777
);

a {
  color: gray(30); // #777
}
```

## File Structure and Naming Conventions

### JavaScript

Child files should have declarations in the following order:

1. Scoped variables
1. FCH hooks
1. Public functions
1. Protected functions

Example:

```javascript

FC.ui = {
  logo_height: 100,

  ready: function() {
    this.setLogoHeight();
  },

  load: function() {
    this.setLogoHeight();
  },

  resize: function() {
    this.setLogoHeight();
  },

  setLogoHeight: function() {
    var logo = this._getLogo();
    this.logo_height = logo.innerHeight;
  },

  _getLogo: function() {
    return document.querySelector('.logo');
  }
}
```

Functions should be lowerCamelCase; variables should be lower_underscore. All functions must include a short description and should be documented using [JSDoc](http://usejsdoc.org/) standards.

```javascript
/**
 * Update value of scoped `logo_height` var
 * @fires _getLogo
 */
setLogoHeight: function() { ... },

/**
 * Find the logo element
 * @protected
 * @return {Node}
 */
_getLogo: function() { ... }
```

## Testing

Install all dependencies:

```bash
npm install --save-dev
```

* `npm test` provides a quick, one-run test as defined in [test/karma.conf.js](test/karma.conf.js)
* `npm run test:dev` opens a Karma instance that watches for file changes, as defined in [test/karma.conf.js](test/local.karma.conf.js)