# @easytool/react-permission
Easy to handle react component permissions.

## Install
```
npm install -S @easytool/react-permission
```

## Usage
```jsx
import Permission from '@easytool/react-permission';
import React from 'react';
import { render } from 'react-dom';

render(
    <Permission hasPermission={[1, 2, 3, 4]}>
        <div permission="1">1</div>
        <div permission="1, 2">1, 2</div>
        <div permission={[1, 2, 3]}>1, 2, 3</div>
        <MyComponent permission={4}>4</MyComponent>
    </Permission>,
    document.getElementById('app')
);
```

### Asynchronous setting permission
```jsx
var promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve([1, 2, 3]);
    }, 3000);
});

render(
    <Permission hasPermission={promise}>
        <div permission="1">1</div>
        <div permission="2">2</div>
        <div permission="3">3</div>
    </Permission>,
    document.getElementById('app')
);
```

### onLoad
```jsx
var promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve([1, 2, 3]);
    }, 3000);
});

render(
    <Permission hasPermission={promise} onLoad={<div>Loading...</div>}>
        <div permission="1">1</div>
        <div permission="2">2</div>
        <div permission="3">3</div>
    </Permission>,
    document.getElementById('app')
);
```

### onDeny
Change denied element.
```jsx
render(
    <Permission hasPermission={[1]} onDeny={el => React.cloneElement(el, { style: { color: 'red' } })}>
        <div permission="1">1</div>
        <MyComponent permission="2">2</MyComponent>
    </Permission>,
    document.getElementById('app')
);
```

Replace denied element.
```jsx
render(
    <Permission hasPermission={[1]} onDeny={<h3>Permission denied</h3>}>
        <div permission="1">1</div>
        <MyComponent permission="2">2</MyComponent>
    </Permission>,
    document.getElementById('app')
);
```

Handle deny to specific element.
```jsx
render(
    <Permission hasPermission={[1]}>
        // DOM Elements can not use 'onxxx' custom attributes, so use 'deny' in place of it.
        <div permission="1" deny={<h3>Permission denied</h3>}>1</div>
        <MyComponent permission="2" onDeny={<h3>Not Allow</h3>}>2</MyComponent>
    </Permission>,
    document.getElementById('app')
);
```

### Use Context Provider
Set global Permission.
```jsx
import Permission, { PermissionContext } from '@easytool/react-permission';

render(
    <PermissionContext.Provider value={{ hasPermission: [1, 2, 3] }}>
        <Permission>
            <div permission="1">1</div>
            <div permission="2">2</div>
        </Permission>
        <Permission>
            <div permission="3">3</div>
            <MyComponent permission="4" />
        </Permission>
    </PermissionContext.Provider>,
    document.getElementById('app')
);
```

Asynchronous setting permission.
```jsx
import Permission, { PermissionContext } from '@easytool/react-permission';

var promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve([1, 2, 3]);
    }, 3000);
});

render(
    <PermissionContext.Provider value={{ hasPermission: promise, onDeny: <h3>Permission denied</h3> }}>
        <Permission>
            <div permission="1">1</div>
            <div permission="2">2</div>
        </Permission>
        <Permission>
            <div permission="3">3</div>
            <MyComponent permission="4" />
        </Permission>
    </PermissionContext.Provider>,
    document.getElementById('app')
);
```

Updating permission from a nested component.
```jsx
class Home extends React.Component {
    static contextType = PermissionContext;

    componentDidMount() {
        setTimeout(() => {
            this.context.togglePermission([1, 2, 3]);
        }, 2000);
    }

    render() {
        return (
            <Permission>
                <h1 permission="1">Home</h1>
                <p permission="2">permission 2</p>
                <p permission="3">permission 3</p>
            </Permission>
        );
    }
}

class App extends Component {
    constructor(props) {
        super(props);

        this.state = {
            hasPermission: [1, 2],
            togglePermission: (permissions) => {
                this.setState({
                    hasPermission: permissions
                });
            }
        };
    }

    render() {
        return (
            <PermissionContext.Provider value={this.state}>
                <Home />
            </PermissionContext.Provider>
        );
    }
}

render(
    <App />,
    document.getElementById('app')
);
```

### Work with React Router
react-router@4-5
```jsx
import Permission, { PermissionContext } from '@easytool/react-permission';

function Deny() {
    return <p>Permission denied</p>;
}

let res, rej;
const context = { 
    hasPermission: new Promise((resolve, reject) => {
        res = resolve;
        rej = reject;
    }),
    onDeny: (el, index) => React.cloneElement(el, { component: Deny })
};

class App extends Component {
    constructor(props) {
        super(props);
    }

    componentDidMount() {
        // request permission
        setTimeout(() => {
            res([1, 2]);
        }, 2000);
    }

    render() {
        return (
            {/* using global setting */}
            <PermissionContext.Provider value={context}>
                <Permission>
                    <HashRouter>
                        <Switch>
                            <Route path="/home" component={Home} permission="2" />
                            {/* this route will be denied  */}
                            <Route path="/list" component={List} permission="3" />                            
                            <Route path="/" component={App} permission="1" />
                        </Switch>
                    </HashRouter>
                </Permission>
            </PermissionContext.Provider>
        );
    }
}

render(
    <App />,
    document.getElementById('app')
);
```

react-router@3
```jsx
function Deny() {
    return <p>Permission denied</p>;
}

render(
    <Permission hasPermission={[1, 2]} onDeny={(el, index) => React.cloneElement(el, { key: index, component: Deny })}>
        <Router history={hashHistory} >
            <Route path="/" permission="1">
                <Route path="/home" component={Home} permission="2" />
                <Route path="/detail" component={Detail} permission="3" />
            </Route>
        </Router>
    </Permission>,
    document.getElementById('app')
);
```

onLoad props is required when use Promise for react-router@3.
```jsx
var promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve([1, 2]);
    }, 3000);
});

render(
    <Permission hasPermission={promise} onLoad={<h1>Loading...</h1>}>
        <Router history={hashHistory} >
            <Route path="/" permission="1">
                <Route path="/home" component={Home} permission="2" />
                <Route path="/detail" component={Detail} permission="3" />
            </Route>
        </Router>
    </Permission>,
    document.getElementById('app')
);
```

### Work with AntD Table
```jsx
import { Table } from 'antd';
import Permission, { PermissionContext } from '@easytool/react-permission';

var dataSource = [{
    key: '1',
    permission: 1,
    name: 'Stephen'
}, {
    key: '2',
    permission: 2,
    name: 'Ricky'
}, {
    key: '3',
    permission: 3,
    name: 'Ray'
}];

render(
    <PermissionContext.Provider value={{ hasPermission: [1, 2] }}>
        <Table dataSource={dataSource}>
            <Column
                title="Name"
                dataIndex="name"
                key="name"
                render={(text, record, index) => {
                    return (
                        <Permission onDeny={<span>Permission denied</span>}>
                            <span permission={record.permission}>{text}</span>
                        </Permission>
                    );
                }}
            />
        </Table>
    </PermissionContext.Provider>,
    document.getElementById('app')
);
```

## API
### Permission
Param|Type|Description
-|-|-
hasPermission|string\|number\|array\|promise|set user's permission.
comparePermission|function|custom compare function, receive(elementPermission, hasPermission). return true means authorized, false means denied.
onLoad|react element|when the hasPermission prop is promise type and state is pending then will use this element to render.
onDeny|react element\|function|this method will be invoked when permission denied occur, function receive denied element and element index in parent children, return a replace element or nothing.
onError|function|when hasPermission is promise and throw error, it will be invoked.

### PermissionContext.Provider
You can specify Context Provider that will be applied to every \<Permission\>.
#### value
Param|Type|Description
-|-|-
hasPermission|string\|number\|array\|promise|as above.
comparePermission|function|as above.
onLoad|react element|as above.
onDeny|react element\|function|as above.
onError|function|as above.

## TODO
1. 用户自己选择查找的子元素, 默认是children
2. 对比方法用户自定义
3. 返回时添加装饰器
4. filterChildren 增加用户自定义判断