All files / boundless/packages/boundless-image index.js

94.44% Statements 17/18
100% Branches 4/4
88.89% Functions 8/9
100% Lines 15/15
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99                                                                                                      5x 1x 1x       8x 14x 8x     9x 9x 9x       22x   9x   9x 9x   9x       22x                                      
import React, {PropTypes} from 'react';
import cx from 'classnames';
 
import omit from 'boundless-utils-omit-keys';
import uuid from 'boundless-utils-uuid';
 
/**
__An image block with placeholder support for loading and fallback scenarios.__
 */
export default class Image extends React.PureComponent {
    static status = {
        LOADING: uuid(),
        LOADED: uuid(),
        ERROR: uuid(),
    }
 
    static propTypes = {
        /**
         * any [React-supported attribute](https://facebook.github.io/react/docs/tags-and-attributes.html#html-attributes)
         */
        '*': PropTypes.any,
 
        /**
         * a written description of the image for search engines, hovertext and those using accessibility technologies
         */
        alt: PropTypes.string,
 
        /**
         * overrides the component HTML tag
         */
        component: PropTypes.string,
 
        /**
         * a valid path to the desired image
         */
        src: PropTypes.string.isRequired,
    }
 
    static defaultProps = {
        alt: '',
        component: 'div',
        src: 'about:blank',
    }
 
    static internalKeys = Object.keys(Image.defaultProps)
 
    state = {
        status: Image.status.LOADING,
    }
 
    componentWillReceiveProps(nextProps) {
        if (nextProps.src !== this.props.src) {
            this.resetPreloader();
            this.setState({status: Image.status.LOADING});
        }
    }
 
    componentDidMount()     { this.preload(); }
    componentDidUpdate()    { this.preload(); }
    componentWillUnmount()  { this.resetPreloader(); }
 
    resetPreloader() {
        this.loader.onload = null;
        this.loader.onerror = null;
        this.loader = null;
    }
 
    preload() {
        if (this.loader) { return; }
 
        this.loader = document.createElement('img');
 
        this.loader.onload = () => this.setState({status: Image.status.LOADED});
        this.loader.onerror = () => this.setState({status: Image.status.ERROR});
 
        this.loader.src = this.props.src;
    }
 
    render() {
        return (
            <this.props.component
                {...omit(this.props, Image.internalKeys)}
                className={cx('b-image', this.props.className, {
                    'b-image-loading': this.state.status === Image.status.LOADING,
                    'b-image-loaded': this.state.status === Image.status.LOADED,
                    'b-image-error': this.state.status === Image.status.ERROR,
                })}
                title={this.props.alt}
                role='img'
                style={{
                    ...this.props.style,
                    backgroundImage: `url(${this.props.src})`,
                }}>
                &nbsp;
            </this.props.component>
        );
    }
}