<commit_before>import React, { Component, PropTypes } from 'react';
import { Tabs, Tab } from 'material-ui/Tabs';
import Checked from 'material-ui/svg-icons/toggle/check-box';
import Unchecked from 'material-ui/svg-icons/toggle/check-box-outline-blank';
import EnhancedButton from 'material-ui/internal/EnhancedButton';
import RaisedButton from 'material-ui/RaisedButton';
import FlatButton from 'material-ui/FlatButton';
import Dialog from 'material-ui/Dialog';
import Clear from 'material-ui/svg-icons/content/clear';
import ArrowDown from 'material-ui/svg-icons/navigation/arrow-drop-down';
import ArrowUp from 'material-ui/svg-icons/navigation/arrow-drop-up';
import Indeterminate from '../../icons/IndeterminateIcon';
import { isViewportS } from '../../../utils/viewport';
import CustomTextField from '../../CustomTextField';
import CustomScrollbar from '../../CustomScrollbar';

export class AddMembersDialog extends Component {
    constructor(props) {
        super(props);
        this.getUnassignedCheckbox = this.getUnassignedCheckbox.bind(this);
        this.getHeaderCheckbox = this.getHeaderCheckbox.bind(this);
        this.handleSave = this.handleSave.bind(this);
        this.handleClose = this.handleClose.bind(this);
        this.handleTabChange = this.handleTabChange.bind(this);
        this.handleCheck = this.handleCheck.bind(this);
        this.handleUncheck = this.handleUncheck.bind(this);
        this.handleSelectAll = this.handleSelectAll.bind(this);
        this.handleDeselectAll = this.handleDeselectAll.bind(this);
        this.handleSearchInput = this.handleSearchInput.bind(this);
        this.handleResize = this.handleResize.bind(this);
        this.toggleSortName = this.toggleSortName.bind(this);
        this.toggleSortSelected = this.toggleSortSelected.bind(this);
        this.state = this.getInitialState();
    }

    getInitialState() {
        return {
            // possible sort values: ['name', '-name', 'selected', '-selected'];
            sort: 'name',
            search: '',
            selection: [],
            tab: 0,
            mobile: isViewportS(),
        };
    }

    componentDidMount() {
        window.addEventListener('resize', this.handleResize);
    }

    componentWillUnmount() {
        window.removeEventListener('resize', this.handleResize);
    }

    getUnassignedCheckbox(group) {
        const checkbox = { height: '28px', width: '28px', cursor: 'pointer' };
        if (this.state.selection.indexOf(group) > -1) {
            return <Checked style={checkbox} onClick={() => { this.handleUncheck(group); }} />;
        }
        return <Unchecked style={checkbox} onClick={() => { this.handleCheck(group); }} />;
    }

    getHeaderCheckbox(groupCount, selectedCount) {
        const checkbox = { height: '28px', width: '28px', cursor: 'pointer' };
        if (groupCount === selectedCount) {
            return <Checked style={checkbox} onClick={this.handleDeselectAll} />;
        } else if (selectedCount > 0) {
            return <Indeterminate style={checkbox} onClick={this.handleSelectAll} />;
        }
        return <Unchecked style={checkbox} onClick={this.handleSelectAll} />;
    }

    getGroupSplit(groups, users) {
        const assigned = [];
        const unassigned = [];
        groups.forEach((group) => {
            const isEveryUserInGroup = users.every(user =>
                group.members.indexOf(user.user.username) > -1);
            if (isEveryUserInGroup) {
                assigned.push(group);
            } else {
                unassigned.push(group);
            }
        });
        return [unassigned, assigned];
    }

    handleSave() {
        this.setState(this.getInitialState());
        this.props.onSave(this.state.selection, this.props.selectedUsers);
    }

    handleClose() {
        this.setState(this.getInitialState());
        this.props.onClose();
    }

    handleTabChange(tab) {
        this.setState({ tab, search: '', sort: 'name' });
    }

    handleCheck(group) {
        const { selection } = this.state;
        selection.push(group);
        this.setState({ selection });
    }

    handleUncheck(group) {
        const { selection } = this.state;
        selection.splice(selection.indexOf(group), 1);
        this.setState({ selection });
    }

    handleSearchInput(e) {
        this.setState({ search: e.target.value });
    }

    handleSelectAll() {
        let { groups } = this.props;
        if (this.state.search) {
            groups = this.searchGroups(groups, this.state.search);
        }
        const selection = this.getGroupSplit(groups, this.props.selectedUsers)[0];
        this.setState({ selection });
    }

    handleDeselectAll() {
        this.setState({ selection: [] });
    }

    handleResize() {
        if (this.state.mobile !== isViewportS()) {
            this.setState({ mobile: !this.state.mobile });
        }
    }

    searchGroups(groups, search) {
        const SEARCH = search.toUpperCase();
        return groups.filter(group => group.name.toUpperCase().includes(SEARCH));
    }

    sortGroupNames(groups, sort) {
        let modifier = 1;
        if (sort === '-name') {
            modifier = -1;
        }
        return groups.sort((a, b) => {
            const nameA = a.name.toUpperCase();
            const nameB = b.name.toUpperCase();
            if (nameA < nameB) return -1 * modifier;
            if (nameA > nameB) return 1 * modifier;
            return 0;
        });
    }

    sortGroupSelected(groups, sort, selection) {
        let modifier = 1;
        if (sort === '-selected') {
            modifier = -1;
        }
        return groups.sort((a, b) => {
            const aSelected = !!selection.find(s => s.id === a.id);
            const bSelected = !!selection.find(s => s.id === b.id);
            if (aSelected && !bSelected) return -1 * modifier;
            if (!aSelected && bSelected) return 1 * modifier;
            return 0;
        });
    }

    toggleSortName() {
        const { sort } = this.state;
        if (sort === 'name') {
            this.setState({ sort: '-name' });
        } else {
            this.setState({ sort: 'name' });
        }
    }

    toggleSortSelected() {
        const { sort } = this.state;
        if (sort === 'selected') {
            this.setState({ sort: '-selected' });
        } else {
            this.setState({ sort: 'selected' });
        }
    }

    render() {
        const styles = {
            dialog: {
                width: 'calc(100% - 32px)',
                height: '100%',
                minWidth: '325px',
                maxWidth: '650px',
                transform: `translate(0px, ${this.state.mobile ? 16 : 64}px)`,
            },
            title: {
                padding: '25px',
                fontWeight: 'none',
                fontSize: '18px',
            },
            body: {
                padding: '0px 25px',
                maxHeight: '100%',
            },
            actions: {
                padding: '25px',
            },
            clear: {
                float: 'right',
                fill: '#4598bf',
                cursor: 'pointer',
            },
            label: {
                color: 'whitesmoke',
                fontWeight: 'bold',
            },
            button: {
                backgroundColor: '#4598bf',
                borderRadius: '0px',
            },
            textField: {
                fontSize: '14px',
                backgroundColor: 'whitesmoke',
                height: '36px',
                lineHeight: '36px',
                margin: '15px 0px 5px',
            },
            characterLimit: {
                bottom: '0px',
                height: '100%',
                display: 'flex',
                transform: 'none',
                alignItems: 'center',
                fontSize: '14px',
            },
            header: {
                display: 'flex',
                width: '100%',
                lineHeight: '28px',
                alignItems: 'center',
                padding: '12px 48px 8px 0px',
                fontSize: '14px',
            },
            arrow: {
                height: '28px',
                width: '28px',
                verticalAlign: 'top',
            },
            unassignedTab: {
                color: '#707274',
                width: '155px',
                borderBottom: this.state.tab === 0 ?
                    '2px solid #4598bf' : '2px #ddd solid',
            },
            assignedTab: {
                color: '#707274',
                width: '155px',
                borderBottom: this.state.tab === 1 ?
                    '2px solid #4598bf' : '2px #ddd solid',
            },
            tabButton: {
                height: '36px',
                padding: '0px 32px',
            },
            row: {
                display: 'flex',
                width: '100%',
                backgroundColor: 'whitesmoke',
                alignItems: 'center',
                padding: '12px 48px 12px 24px',
                marginBottom: '10px',
            },
            name: {
                flex: '1 1 auto',
                fontWeight: 800,
                color: '#000',
            },
        };

        let { groups } = this.props;
        if (this.state.search) {
            groups = this.searchGroups(groups, this.state.search);
        }
        groups = this.sortGroupNames(groups, this.state.sort);

        if (this.state.sort.includes('selected')) {
            groups = this.sortGroupSelected(groups, this.state.sort, this.state.selection);
        }

        const [unassigned, assigned] = this.getGroupSplit(groups, this.props.selectedUsers);

        const searchbar = (
            <CustomTextField
                className="qa-AddMembersDialog-search"
                fullWidth
                maxLength={50}
                hintText="Search"
                onChange={this.handleSearchInput}
                value={this.state.search}
                inputStyle={{ paddingLeft: '16px' }}
                style={styles.textField}
                underlineShow={false}
                hintStyle={{ paddingLeft: '16px', bottom: '0px', color: '#707274' }}
                charsRemainingStyle={styles.characterLimit}
            />
        );

        const sortName = label => (
            <EnhancedButton
                onClick={this.toggleSortName}
                style={{
                    padding: '0px 10px',
                    color: this.state.sort.includes('name') ? '#4598bf' : 'inherit',
                }}
                className="qa-AddMembersDialog-sortName"
            >
                {label}
                {this.state.sort === '-name' ?
                    <ArrowUp style={styles.arrow} />
                    :
                    <ArrowDown style={styles.arrow} />
                }
            </EnhancedButton>
        );

        const sortSelected = (
            <EnhancedButton
                onClick={this.toggleSortSelected}
                style={{
                    padding: '0px 10px',
                    color: this.state.sort.includes('selected') ? '#4598bf' : 'inherit',
                }}
                className="qa-AddMembersDialog-sortSelected"
            >
                {this.state.sort === '-selected' ?
                    <ArrowUp style={styles.arrow} />
                    :
                    <ArrowDown style={styles.arrow} />
                }
                {`(${this.state.selection.length}) SELECTED`}
            </EnhancedButton>
        );

        const actions = [
            <RaisedButton
                className="qa-AddMembersDialog-save"
                style={{ margin: '0px' }}
                labelStyle={{ color: 'whitesmoke', fontWeight: 'bold' }}
                buttonStyle={{ borderRadius: '0px' }}
                backgroundColor="#4598bf"
                disableTouchRipple
                label="SAVE"
                primary={false}
                onClick={this.handleSave}
                disabled={false}
            />,
            <FlatButton
                className="qa-AddMembersDialog-cancel"
                style={{ margin: '0px', float: 'left' }}
                labelStyle={{ color: '#4598bf', fontWeight: 'bold' }}
                backgroundColor="#fff"
                disableTouchRipple
                label="CANCEL"
                onClick={this.handleClose}
            />,
        ];

        const title = (

                ASSIGN TO EXISTING GROUPS
                <Clear style={styles.clear} onClick={this.handleClose} />

        );

        return (
            <Dialog
                className="qa-AddMembersDialog"
                contentStyle={styles.dialog}
                bodyStyle={styles.body}
                actions={actions}
                style={{ paddingTop: '0px' }}
                modal
                open={this.props.show}
                onRequestClose={this.handleClose}
                title={title}
                titleStyle={styles.title}
                actionsContainerStyle={styles.actions}
                autoDetectWindowHeight={false}
                repositionOnUpdate={false}
            >

                        Here, you may only assign members to existing groups one time.

                     To edit group member assignments, you must go to the member's assigned group.
                     You may view user group assignments below under the 'ASSIGNED' tab.

                <Tabs
                    value={this.state.tab}
                    onChange={this.handleTabChange}
                    inkBarStyle={{ backgroundColor: '#ddd', width: '100%', left: '0%' }}
                    tabItemContainerStyle={{ backgroundColor: '#fff' }}
                    className="qa-AddMembersDialog-Tabs"
                >
                    <Tab
                        label="UNASSIGNED"
                        value={0}
                        style={styles.unassignedTab}
                        buttonStyle={styles.tabButton}
                        className="qa-AddMembersDialog-Tab-unassigned"
                    >
                        {searchbar}

                                {sortName('NAME')}

                            {sortSelected}

                                {this.getHeaderCheckbox(unassigned.length, this.state.selection.length)}

                        <CustomScrollbar
                            style={{ height: this.state.mobile ? window.innerHeight - 457 : window.innerHeight - 553 }}
                        >
                            {unassigned.map(group => (

{group.name}

                                    {this.getUnassignedCheckbox(group)}

                            ))}
                        </CustomScrollbar>
                    </Tab>
                    <Tab
                        label="ASSIGNED"
                        value={1}
                        style={styles.assignedTab}
                        buttonStyle={styles.tabButton}
                        className="qa-AddMembersDialog-Tab-assigned"
                    >
                        {searchbar}

                                {sortName(`GROUP ASSIGNMENTS (${assigned.length})`)}

                                (View Only)

                        <CustomScrollbar
                            style={{ height: this.state.mobile ? window.innerHeight - 457 : window.innerHeight - 553 }}
                        >
                            {assigned.map(group => (

{group.name}

                                    <Checked style={{ height: '28px', width: '28px', fill: '#9d9d9d' }} />

                            ))}
                        </CustomScrollbar>
                    </Tab>
                </Tabs>
            </Dialog>
        );
    }
}

AddMembersDialog.propTypes = {
    show: PropTypes.bool.isRequired,
    onClose: PropTypes.func.isRequired,
    onSave: PropTypes.func.isRequired,
    groups: PropTypes.arrayOf(PropTypes.shape({
        id: PropTypes.number,
        name: PropTypes.string,
        members: PropTypes.arrayOf(PropTypes.string),
        administrators: PropTypes.arrayOf(PropTypes.string),
    })).isRequired,
    selectedUsers: PropTypes.arrayOf(PropTypes.shape({
        user: PropTypes.shape({
            username: PropTypes.string,
            first_name: PropTypes.string,
            last_name: PropTypes.string,
            email: PropTypes.string,
            last_login: PropTypes.string,
            date_joined: PropTypes.string,
        }),
        groups: PropTypes.arrayOf(PropTypes.number),
    })).isRequired,
};

export default AddMembersDialog;
<commit_msg>change add members text<commit_after>import React, { Component, PropTypes } from 'react';
import { Tabs, Tab } from 'material-ui/Tabs';
import Checked from 'material-ui/svg-icons/toggle/check-box';
import Unchecked from 'material-ui/svg-icons/toggle/check-box-outline-blank';
import EnhancedButton from 'material-ui/internal/EnhancedButton';
import RaisedButton from 'material-ui/RaisedButton';
import FlatButton from 'material-ui/FlatButton';
import Dialog from 'material-ui/Dialog';
import Clear from 'material-ui/svg-icons/content/clear';
import ArrowDown from 'material-ui/svg-icons/navigation/arrow-drop-down';
import ArrowUp from 'material-ui/svg-icons/navigation/arrow-drop-up';
import Indeterminate from '../../icons/IndeterminateIcon';
import { isViewportS } from '../../../utils/viewport';
import CustomTextField from '../../CustomTextField';
import CustomScrollbar from '../../CustomScrollbar';

export class AddMembersDialog extends Component {
    constructor(props) {
        super(props);
        this.getUnassignedCheckbox = this.getUnassignedCheckbox.bind(this);
        this.getHeaderCheckbox = this.getHeaderCheckbox.bind(this);
        this.handleSave = this.handleSave.bind(this);
        this.handleClose = this.handleClose.bind(this);
        this.handleTabChange = this.handleTabChange.bind(this);
        this.handleCheck = this.handleCheck.bind(this);
        this.handleUncheck = this.handleUncheck.bind(this);
        this.handleSelectAll = this.handleSelectAll.bind(this);
        this.handleDeselectAll = this.handleDeselectAll.bind(this);
        this.handleSearchInput = this.handleSearchInput.bind(this);
        this.handleResize = this.handleResize.bind(this);
        this.toggleSortName = this.toggleSortName.bind(this);
        this.toggleSortSelected = this.toggleSortSelected.bind(this);
        this.state = this.getInitialState();
    }

    getInitialState() {
        return {
            // possible sort values: ['name', '-name', 'selected', '-selected'];
            sort: 'name',
            search: '',
            selection: [],
            tab: 0,
            mobile: isViewportS(),
        };
    }

    componentDidMount() {
        window.addEventListener('resize', this.handleResize);
    }

    componentWillUnmount() {
        window.removeEventListener('resize', this.handleResize);
    }

    getUnassignedCheckbox(group) {
        const checkbox = { height: '28px', width: '28px', cursor: 'pointer' };
        if (this.state.selection.indexOf(group) > -1) {
            return <Checked style={checkbox} onClick={() => { this.handleUncheck(group); }} />;
        }
        return <Unchecked style={checkbox} onClick={() => { this.handleCheck(group); }} />;
    }

    getHeaderCheckbox(groupCount, selectedCount) {
        const checkbox = { height: '28px', width: '28px', cursor: 'pointer' };
        if (groupCount === selectedCount) {
            return <Checked style={checkbox} onClick={this.handleDeselectAll} />;
        } else if (selectedCount > 0) {
            return <Indeterminate style={checkbox} onClick={this.handleSelectAll} />;
        }
        return <Unchecked style={checkbox} onClick={this.handleSelectAll} />;
    }

    getGroupSplit(groups, users) {
        const assigned = [];
        const unassigned = [];
        groups.forEach((group) => {
            const isEveryUserInGroup = users.every(user =>
                group.members.indexOf(user.user.username) > -1);
            if (isEveryUserInGroup) {
                assigned.push(group);
            } else {
                unassigned.push(group);
            }
        });
        return [unassigned, assigned];
    }

    handleSave() {
        this.setState(this.getInitialState());
        this.props.onSave(this.state.selection, this.props.selectedUsers);
    }

    handleClose() {
        this.setState(this.getInitialState());
        this.props.onClose();
    }

    handleTabChange(tab) {
        this.setState({ tab, search: '', sort: 'name' });
    }

    handleCheck(group) {
        const { selection } = this.state;
        selection.push(group);
        this.setState({ selection });
    }

    handleUncheck(group) {
        const { selection } = this.state;
        selection.splice(selection.indexOf(group), 1);
        this.setState({ selection });
    }

    handleSearchInput(e) {
        this.setState({ search: e.target.value });
    }

    handleSelectAll() {
        let { groups } = this.props;
        if (this.state.search) {
            groups = this.searchGroups(groups, this.state.search);
        }
        const selection = this.getGroupSplit(groups, this.props.selectedUsers)[0];
        this.setState({ selection });
    }

    handleDeselectAll() {
        this.setState({ selection: [] });
    }

    handleResize() {
        if (this.state.mobile !== isViewportS()) {
            this.setState({ mobile: !this.state.mobile });
        }
    }

    searchGroups(groups, search) {
        const SEARCH = search.toUpperCase();
        return groups.filter(group => group.name.toUpperCase().includes(SEARCH));
    }

    sortGroupNames(groups, sort) {
        let modifier = 1;
        if (sort === '-name') {
            modifier = -1;
        }
        return groups.sort((a, b) => {
            const nameA = a.name.toUpperCase();
            const nameB = b.name.toUpperCase();
            if (nameA < nameB) return -1 * modifier;
            if (nameA > nameB) return 1 * modifier;
            return 0;
        });
    }

    sortGroupSelected(groups, sort, selection) {
        let modifier = 1;
        if (sort === '-selected') {
            modifier = -1;
        }
        return groups.sort((a, b) => {
            const aSelected = !!selection.find(s => s.id === a.id);
            const bSelected = !!selection.find(s => s.id === b.id);
            if (aSelected && !bSelected) return -1 * modifier;
            if (!aSelected && bSelected) return 1 * modifier;
            return 0;
        });
    }

    toggleSortName() {
        const { sort } = this.state;
        if (sort === 'name') {
            this.setState({ sort: '-name' });
        } else {
            this.setState({ sort: 'name' });
        }
    }

    toggleSortSelected() {
        const { sort } = this.state;
        if (sort === 'selected') {
            this.setState({ sort: '-selected' });
        } else {
            this.setState({ sort: 'selected' });
        }
    }

    render() {
        const styles = {
            dialog: {
                width: 'calc(100% - 32px)',
                height: '100%',
                minWidth: '325px',
                maxWidth: '650px',
                transform: `translate(0px, ${this.state.mobile ? 16 : 64}px)`,
            },
            title: {
                padding: '25px',
                fontWeight: 'none',
                fontSize: '18px',
            },
            body: {
                padding: '0px 25px',
                maxHeight: '100%',
            },
            actions: {
                padding: '25px',
            },
            clear: {
                float: 'right',
                fill: '#4598bf',
                cursor: 'pointer',
            },
            label: {
                color: 'whitesmoke',
                fontWeight: 'bold',
            },
            button: {
                backgroundColor: '#4598bf',
                borderRadius: '0px',
            },
            textField: {
                fontSize: '14px',
                backgroundColor: 'whitesmoke',
                height: '36px',
                lineHeight: '36px',
                margin: '15px 0px 5px',
            },
            characterLimit: {
                bottom: '0px',
                height: '100%',
                display: 'flex',
                transform: 'none',
                alignItems: 'center',
                fontSize: '14px',
            },
            header: {
                display: 'flex',
                width: '100%',
                lineHeight: '28px',
                alignItems: 'center',
                padding: '12px 48px 8px 0px',
                fontSize: '14px',
            },
            arrow: {
                height: '28px',
                width: '28px',
                verticalAlign: 'top',
            },
            unassignedTab: {
                color: '#707274',
                width: '175px',
                borderBottom: this.state.tab === 0 ?
                    '2px solid #4598bf' : '2px #ddd solid',
            },
            assignedTab: {
                color: '#707274',
                width: '175px',
                borderBottom: this.state.tab === 1 ?
                    '2px solid #4598bf' : '2px #ddd solid',
            },
            tabButton: {
                height: '36px',
                padding: '0px 32px',
            },
            row: {
                display: 'flex',
                width: '100%',
                backgroundColor: 'whitesmoke',
                alignItems: 'center',
                padding: '12px 48px 12px 24px',
                marginBottom: '10px',
            },
            name: {
                flex: '1 1 auto',
                fontWeight: 800,
                color: '#000',
            },
        };

        let { groups } = this.props;
        if (this.state.search) {
            groups = this.searchGroups(groups, this.state.search);
        }
        groups = this.sortGroupNames(groups, this.state.sort);

        if (this.state.sort.includes('selected')) {
            groups = this.sortGroupSelected(groups, this.state.sort, this.state.selection);
        }

        const [unassigned, assigned] = this.getGroupSplit(groups, this.props.selectedUsers);

        const searchbar = (
            <CustomTextField
                className="qa-AddMembersDialog-search"
                fullWidth
                maxLength={50}
                hintText="Search"
                onChange={this.handleSearchInput}
                value={this.state.search}
                inputStyle={{ paddingLeft: '16px' }}
                style={styles.textField}
                underlineShow={false}
                hintStyle={{ paddingLeft: '16px', bottom: '0px', color: '#707274' }}
                charsRemainingStyle={styles.characterLimit}
            />
        );

        const sortName = label => (
            <EnhancedButton
                onClick={this.toggleSortName}
                style={{
                    padding: '0px 10px',
                    color: this.state.sort.includes('name') ? '#4598bf' : 'inherit',
                }}
                className="qa-AddMembersDialog-sortName"
            >
                {label}
                {this.state.sort === '-name' ?
                    <ArrowUp style={styles.arrow} />
                    :
                    <ArrowDown style={styles.arrow} />
                }
            </EnhancedButton>
        );

        const sortSelected = (
            <EnhancedButton
                onClick={this.toggleSortSelected}
                style={{
                    padding: '0px 10px',
                    color: this.state.sort.includes('selected') ? '#4598bf' : 'inherit',
                }}
                className="qa-AddMembersDialog-sortSelected"
            >
                {this.state.sort === '-selected' ?
                    <ArrowUp style={styles.arrow} />
                    :
                    <ArrowDown style={styles.arrow} />
                }
                {`(${this.state.selection.length}) SELECTED`}
            </EnhancedButton>
        );

        const actions = [
            <RaisedButton
                className="qa-AddMembersDialog-save"
                style={{ margin: '0px' }}
                labelStyle={{ color: 'whitesmoke', fontWeight: 'bold' }}
                buttonStyle={{ borderRadius: '0px' }}
                backgroundColor="#4598bf"
                disableTouchRipple
                label="SAVE"
                primary={false}
                onClick={this.handleSave}
                disabled={false}
            />,
            <FlatButton
                className="qa-AddMembersDialog-cancel"
                style={{ margin: '0px', float: 'left' }}
                labelStyle={{ color: '#4598bf', fontWeight: 'bold' }}
                backgroundColor="#fff"
                disableTouchRipple
                label="CANCEL"
                onClick={this.handleClose}
            />,
        ];

        const title = (

                ADD TO EXISTING GROUPS
                <Clear style={styles.clear} onClick={this.handleClose} />

        );

        return (
            <Dialog
                className="qa-AddMembersDialog"
                contentStyle={styles.dialog}
                bodyStyle={styles.body}
                actions={actions}
                style={{ paddingTop: '0px' }}
                modal
                open={this.props.show}
                onRequestClose={this.handleClose}
                title={title}
                titleStyle={styles.title}
                actionsContainerStyle={styles.actions}
                autoDetectWindowHeight={false}
                repositionOnUpdate={false}
            >

                    You can add selected members to the groups listed in the 'AVAILABLE GROUPS' tab.
                     For additional reference, groups that already contain those selected members are listed in the 'ALREADY IN GROUPS' tab.
                     To make further edits, go to the individual group on the Members and Groups page.

                <Tabs
                    value={this.state.tab}
                    onChange={this.handleTabChange}
                    inkBarStyle={{ backgroundColor: '#ddd', width: '100%', left: '0%' }}
                    tabItemContainerStyle={{ backgroundColor: '#fff' }}
                    className="qa-AddMembersDialog-Tabs"
                >
                    <Tab
                        label="AVAILABLE GROUPS"
                        value={0}
                        style={styles.unassignedTab}
                        buttonStyle={styles.tabButton}
                        className="qa-AddMembersDialog-Tab-unassigned"
                    >
                        {searchbar}

                                {sortName('NAME')}

                            {sortSelected}

                                {this.getHeaderCheckbox(unassigned.length, this.state.selection.length)}

                        <CustomScrollbar
                            style={{ height: this.state.mobile ? window.innerHeight - 457 : window.innerHeight - 553 }}
                        >
                            {unassigned.map(group => (

{group.name}

                                    {this.getUnassignedCheckbox(group)}

                            ))}
                        </CustomScrollbar>
                    </Tab>
                    <Tab
                        label="ALREADY IN GROUPS"
                        value={1}
                        style={styles.assignedTab}
                        buttonStyle={styles.tabButton}
                        className="qa-AddMembersDialog-Tab-assigned"
                    >
                        {searchbar}

                                {sortName(`GROUP ASSIGNMENTS (${assigned.length})`)}

                                (View Only)

                        <CustomScrollbar
                            style={{ height: this.state.mobile ? window.innerHeight - 457 : window.innerHeight - 553 }}
                        >
                            {assigned.map(group => (

{group.name}

                                    <Checked style={{ height: '28px', width: '28px', fill: '#9d9d9d' }} />

                            ))}
                        </CustomScrollbar>
                    </Tab>
                </Tabs>
            </Dialog>
        );
    }
}

AddMembersDialog.propTypes = {
    show: PropTypes.bool.isRequired,
    onClose: PropTypes.func.isRequired,
    onSave: PropTypes.func.isRequired,
    groups: PropTypes.arrayOf(PropTypes.shape({
        id: PropTypes.number,
        name: PropTypes.string,
        members: PropTypes.arrayOf(PropTypes.string),
        administrators: PropTypes.arrayOf(PropTypes.string),
    })).isRequired,
    selectedUsers: PropTypes.arrayOf(PropTypes.shape({
        user: PropTypes.shape({
            username: PropTypes.string,
            first_name: PropTypes.string,
            last_name: PropTypes.string,
            email: PropTypes.string,
            last_login: PropTypes.string,
            date_joined: PropTypes.string,
        }),
        groups: PropTypes.arrayOf(PropTypes.number),
    })).isRequired,
};

export default AddMembersDialog;
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2018 - present Instructure, Inc.
 *
 * This file is part of Canvas.
 *
 * Canvas is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, version 3 of the License.
 *
 * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */

import CoreTranslations from '../public/javascripts/translations/en.json'
import Enzyme from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import {filterUselessConsoleMessages} from '@instructure/js-utils'
import rceFormatMessage from '@instructure/canvas-rce/lib/format-message'
import plannerFormatMessage from '@instructure/canvas-planner/lib/format-message'
import {up as configureDateTime} from '../ui/boot/initializers/configureDateTime'
import {up as configureDateTimeMomentParser} from '../ui/boot/initializers/configureDateTimeMomentParser'
import {useTranslations} from '@canvas/i18n'

useTranslations('en', CoreTranslations)

rceFormatMessage.setup({
  locale: 'en',
  missingTranslation: 'ignore',
})

plannerFormatMessage.setup({
  locale: 'en',
  missingTranslation: 'ignore',
})

/**
 * We want to ensure errors and warnings get appropriate eyes. If
 * you are seeing an exception from here, it probably means you
 * have an unintended consequence from your changes. If you expect
 * the warning/error, add it to the ignore list below.
 */
/* eslint-disable no-console */
const globalError = global.console.error
const ignoredErrors = [
  /\[object Object\]/,
  /%s has a method called shouldComponentUpdate/,
  /`NaN` is an invalid value for the `%s` css style property/,
  /<Provider> does not support changing `store` on the fly/,
  /A component is changing a controlled input of type %s to be uncontrolled/,
  /A theme registry has already been initialized/,
  /An update to (%s|DefaultToolForm) inside a test was not wrapped in act/,
  /Can't perform a React state update on an unmounted component/,
  /Cannot read property '(activeElement|useRealTimers)' of undefined/,
  /Cannot read property 'name' of null/,
  /Cannot update during an existing state transition/,
  /ColorPicker: isMounted is deprecated/,
  /contextType was defined as an instance property on %s/,
  /Error writing result to store for query/,
  /Expected one of Group, Option in Select but found 'option'/,
  /Failed loading the language file for/,
  /Function components cannot be given refs/,
  /invalid messageType: (notSupported|undefined)/,
  /Invalid prop `children` supplied to `(Option|View)`/,
  /Invalid prop `editorOptions.plugins` of type `string` supplied to `(ForwardRef|RCEWrapper)`/, // https://instructure.atlassian.net/browse/MAT-453
  /Invalid prop `editorOptions.toolbar\[0\]` of type `string` supplied to `(ForwardRef|RCEWrapper)`/, // https://instructure.atlassian.net/browse/MAT-453
  /Invalid prop `heading` of type `object` supplied to `Billboard`/, // https://instructure.atlassian.net/browse/QUIZ-8870
  /Invalid prop `returnFocusTo` of type `DeprecatedComponent` supplied to `HomePagePromptContainer`/,
  /Invalid prop `selectedDate` of type `date` supplied to `CanvasDateInput`/,
  /Invariant Violation/,
  /It looks like you're using the wrong act/,
  /Prop `children` should be supplied unless/,
  /React does not recognize the `%s` prop on a DOM element/,
  /Render methods should be a pure function of props and state/,
  /The 'screenReaderOnly' prop must be used in conjunction with 'liveRegion'/,
  /The above error occurred in the <.*> component/,
  /The prop `focusOnInit` is marked as required in `(FileUpload|TextEntry|UrlEntry)`/,
  /The prop `id` is marked as required in `(ColHeader|FormField|Option)`/,
  /The prop `label` is marked as required in `(FormField|FormFieldLayout|Modal)`/,
  /The prop `renderLabel` is marked as required in `Select`/,
  /validateDOMNesting\(...\): %s cannot appear as a child of <%s>/,
  /WARNING: heuristic fragment matching going on!/,
  /Warning: Failed prop type: Expected one of Checkbox in CheckboxGroup but found `View`/,
  /You are using the simple \(heuristic\) fragment matcher, but your queries contain union or interface types./,
  /You seem to have overlapping act\(\) calls/,
]
const globalWarn = global.console.warn
const ignoredWarnings = [
  /\[View|Button|Text\] .* in version 8.0.0/i,
  /Error getting \/media_objects\/dummy_media_id\/info/,
  /Exactly one focusable child is required/,
  /Please update the following components: %s/,
  /shared_brand_configs.* not called/,
  /value provided is not in a recognized RFC2822 or ISO format/,
]
global.console = {
  log: console.log,
  error: error => {
    if (ignoredErrors.some(regex => regex.test(error))) {
      return
    }
    globalError(error)
    throw new Error(
      `Looks like you have an unhandled error. Keep our test logs clean by handling or filtering it. ${error}`
    )
  },
  warn: warning => {
    if (ignoredWarnings.some(regex => regex.test(warning))) {
      return
    }
    globalWarn(warning)
    throw new Error(
      `Looks like you have an unhandled warning. Keep our test logs clean by handling or filtering it. ${warning}`
    )
  },
  info: console.info,
  debug: console.debug,
}
/* eslint-enable no-console */
filterUselessConsoleMessages(global.console)

require('jest-fetch-mock').enableFetchMocks()

window.scroll = () => {}
window.ENV = {
  use_rce_enhancements: true,
  FEATURES: {
    extended_submission_state: true,
  },
}

Enzyme.configure({adapter: new Adapter()})

// because InstUI themeable components need an explicit "dir" attribute on the <html> element
document.documentElement.setAttribute('dir', 'ltr')

configureDateTime()
configureDateTimeMomentParser()

// because everyone implements `flat()` and `flatMap()` except JSDOM 🤦🏼♂
if (!Array.prototype.flat) {
  // eslint-disable-next-line no-extend-native
  Object.defineProperty(Array.prototype, 'flat', {
    configurable: true,
    value: function flat(depth = 1) {
      if (depth === 0) return this.slice()
      return this.reduce(function (acc, cur) {
        if (Array.isArray(cur)) {
          acc.push(...flat.call(cur, depth - 1))
        } else {
          acc.push(cur)
        }
        return acc
      }, [])
    },
    writable: true,
  })
}

if (!Array.prototype.flatMap) {
  // eslint-disable-next-line no-extend-native
  Object.defineProperty(Array.prototype, 'flatMap', {
    configurable: true,
    value: function flatMap(_cb) {
      return Array.prototype.map.apply(this, arguments).flat()
    },
    writable: true,
  })
}

require('@instructure/ui-themes')

// set up mocks for native APIs
if (!('MutationObserver' in window)) {
  Object.defineProperty(window, 'MutationObserver', {
    value: require('@sheerun/mutationobserver-shim'),
  })
}

if (!('IntersectionObserver' in window)) {
  Object.defineProperty(window, 'IntersectionObserver', {
    writable: true,
    configurable: true,
    value: class IntersectionObserver {
      disconnect() {
        return null
      }

      observe() {
        return null
      }

      takeRecords() {
        return null
      }

      unobserve() {
        return null
      }
    },
  })
}

if (!('ResizeObserver' in window)) {
  Object.defineProperty(window, 'ResizeObserver', {
    writable: true,
    configurable: true,
    value: class IntersectionObserver {
      observe() {
        return null
      }

      unobserve() {
        return null
      }
    },
  })
}

if (!('matchMedia' in window)) {
  window.matchMedia = () => ({
    matches: false,
    addListener: () => {},
    removeListener: () => {},
  })
  window.matchMedia._mocked = true
}

if (!('scrollIntoView' in window.HTMLElement.prototype)) {
  window.HTMLElement.prototype.scrollIntoView = () => {}
}

// Suppress errors for APIs that exist in JSDOM but aren't implemented
Object.defineProperty(window, 'scrollTo', {configurable: true, writable: true, value: () => {}})

const locationProperties = Object.getOwnPropertyDescriptors(window.location)
Object.defineProperty(window, 'location', {
  configurable: true,
  enumerable: true,
  get: () =>
    Object.defineProperties(
      {},
      {
        ...locationProperties,
        href: {
          ...locationProperties.href,
          // Prevents JSDOM errors from doing window.location.href = ...
          set: () => {},
        },
        reload: {
          configurable: true,
          enumerable: true,
          writeable: true,
          // Prevents JSDOM errors from doing window.location.reload()
          value: () => {},
        },
      }
    ),
  // Prevents JSDOM errors from doing window.location = ...
  set: () => {},
})
<commit_msg>remove unnecessary HomePagePromptContainer warning supression<commit_after>/*
 * Copyright (C) 2018 - present Instructure, Inc.
 *
 * This file is part of Canvas.
 *
 * Canvas is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, version 3 of the License.
 *
 * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */

import CoreTranslations from '../public/javascripts/translations/en.json'
import Enzyme from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import {filterUselessConsoleMessages} from '@instructure/js-utils'
import rceFormatMessage from '@instructure/canvas-rce/lib/format-message'
import plannerFormatMessage from '@instructure/canvas-planner/lib/format-message'
import {up as configureDateTime} from '../ui/boot/initializers/configureDateTime'
import {up as configureDateTimeMomentParser} from '../ui/boot/initializers/configureDateTimeMomentParser'
import {useTranslations} from '@canvas/i18n'

useTranslations('en', CoreTranslations)

rceFormatMessage.setup({
  locale: 'en',
  missingTranslation: 'ignore',
})

plannerFormatMessage.setup({
  locale: 'en',
  missingTranslation: 'ignore',
})

/**
 * We want to ensure errors and warnings get appropriate eyes. If
 * you are seeing an exception from here, it probably means you
 * have an unintended consequence from your changes. If you expect
 * the warning/error, add it to the ignore list below.
 */
/* eslint-disable no-console */
const globalError = global.console.error
const ignoredErrors = [
  /\[object Object\]/,
  /%s has a method called shouldComponentUpdate/,
  /`NaN` is an invalid value for the `%s` css style property/,
  /<Provider> does not support changing `store` on the fly/,
  /A component is changing a controlled input of type %s to be uncontrolled/,
  /A theme registry has already been initialized/,
  /An update to (%s|DefaultToolForm) inside a test was not wrapped in act/,
  /Can't perform a React state update on an unmounted component/,
  /Cannot read property '(activeElement|useRealTimers)' of undefined/,
  /Cannot read property 'name' of null/,
  /Cannot update during an existing state transition/,
  /ColorPicker: isMounted is deprecated/,
  /contextType was defined as an instance property on %s/,
  /Error writing result to store for query/,
  /Expected one of Group, Option in Select but found 'option'/,
  /Failed loading the language file for/,
  /Function components cannot be given refs/,
  /invalid messageType: (notSupported|undefined)/,
  /Invalid prop `children` supplied to `(Option|View)`/,
  /Invalid prop `editorOptions.plugins` of type `string` supplied to `(ForwardRef|RCEWrapper)`/, // https://instructure.atlassian.net/browse/MAT-453
  /Invalid prop `editorOptions.toolbar\[0\]` of type `string` supplied to `(ForwardRef|RCEWrapper)`/, // https://instructure.atlassian.net/browse/MAT-453
  /Invalid prop `heading` of type `object` supplied to `Billboard`/, // https://instructure.atlassian.net/browse/QUIZ-8870
  /Invalid prop `selectedDate` of type `date` supplied to `CanvasDateInput`/,
  /Invariant Violation/,
  /It looks like you're using the wrong act/,
  /Prop `children` should be supplied unless/,
  /React does not recognize the `%s` prop on a DOM element/,
  /Render methods should be a pure function of props and state/,
  /The 'screenReaderOnly' prop must be used in conjunction with 'liveRegion'/,
  /The above error occurred in the <.*> component/,
  /The prop `focusOnInit` is marked as required in `(FileUpload|TextEntry|UrlEntry)`/,
  /The prop `id` is marked as required in `(ColHeader|FormField|Option)`/,
  /The prop `label` is marked as required in `(FormField|FormFieldLayout|Modal)`/,
  /The prop `renderLabel` is marked as required in `Select`/,
  /validateDOMNesting\(...\): %s cannot appear as a child of <%s>/,
  /WARNING: heuristic fragment matching going on!/,
  /Warning: Failed prop type: Expected one of Checkbox in CheckboxGroup but found `View`/,
  /You are using the simple \(heuristic\) fragment matcher, but your queries contain union or interface types./,
  /You seem to have overlapping act\(\) calls/,
]
const globalWarn = global.console.warn
const ignoredWarnings = [
  /\[View|Button|Text\] .* in version 8.0.0/i,
  /Error getting \/media_objects\/dummy_media_id\/info/,
  /Exactly one focusable child is required/,
  /Please update the following components: %s/,
  /shared_brand_configs.* not called/,
  /value provided is not in a recognized RFC2822 or ISO format/,
]
global.console = {
  log: console.log,
  error: error => {
    if (ignoredErrors.some(regex => regex.test(error))) {
      return
    }
    globalError(error)
    throw new Error(
      `Looks like you have an unhandled error. Keep our test logs clean by handling or filtering it. ${error}`
    )
  },
  warn: warning => {
    if (ignoredWarnings.some(regex => regex.test(warning))) {
      return
    }
    globalWarn(warning)
    throw new Error(
      `Looks like you have an unhandled warning. Keep our test logs clean by handling or filtering it. ${warning}`
    )
  },
  info: console.info,
  debug: console.debug,
}
/* eslint-enable no-console */
filterUselessConsoleMessages(global.console)

require('jest-fetch-mock').enableFetchMocks()

window.scroll = () => {}
window.ENV = {
  use_rce_enhancements: true,
  FEATURES: {
    extended_submission_state: true,
  },
}

Enzyme.configure({adapter: new Adapter()})

// because InstUI themeable components need an explicit "dir" attribute on the <html> element
document.documentElement.setAttribute('dir', 'ltr')

configureDateTime()
configureDateTimeMomentParser()

// because everyone implements `flat()` and `flatMap()` except JSDOM 🤦🏼♂
if (!Array.prototype.flat) {
  // eslint-disable-next-line no-extend-native
  Object.defineProperty(Array.prototype, 'flat', {
    configurable: true,
    value: function flat(depth = 1) {
      if (depth === 0) return this.slice()
      return this.reduce(function (acc, cur) {
        if (Array.isArray(cur)) {
          acc.push(...flat.call(cur, depth - 1))
        } else {
          acc.push(cur)
        }
        return acc
      }, [])
    },
    writable: true,
  })
}

if (!Array.prototype.flatMap) {
  // eslint-disable-next-line no-extend-native
  Object.defineProperty(Array.prototype, 'flatMap', {
    configurable: true,
    value: function flatMap(_cb) {
      return Array.prototype.map.apply(this, arguments).flat()
    },
    writable: true,
  })
}

require('@instructure/ui-themes')

// set up mocks for native APIs
if (!('MutationObserver' in window)) {
  Object.defineProperty(window, 'MutationObserver', {
    value: require('@sheerun/mutationobserver-shim'),
  })
}

if (!('IntersectionObserver' in window)) {
  Object.defineProperty(window, 'IntersectionObserver', {
    writable: true,
    configurable: true,
    value: class IntersectionObserver {
      disconnect() {
        return null
      }

      observe() {
        return null
      }

      takeRecords() {
        return null
      }

      unobserve() {
        return null
      }
    },
  })
}

if (!('ResizeObserver' in window)) {
  Object.defineProperty(window, 'ResizeObserver', {
    writable: true,
    configurable: true,
    value: class IntersectionObserver {
      observe() {
        return null
      }

      unobserve() {
        return null
      }
    },
  })
}

if (!('matchMedia' in window)) {
  window.matchMedia = () => ({
    matches: false,
    addListener: () => {},
    removeListener: () => {},
  })
  window.matchMedia._mocked = true
}

if (!('scrollIntoView' in window.HTMLElement.prototype)) {
  window.HTMLElement.prototype.scrollIntoView = () => {}
}

// Suppress errors for APIs that exist in JSDOM but aren't implemented
Object.defineProperty(window, 'scrollTo', {configurable: true, writable: true, value: () => {}})

const locationProperties = Object.getOwnPropertyDescriptors(window.location)
Object.defineProperty(window, 'location', {
  configurable: true,
  enumerable: true,
  get: () =>
    Object.defineProperties(
      {},
      {
        ...locationProperties,
        href: {
          ...locationProperties.href,
          // Prevents JSDOM errors from doing window.location.href = ...
          set: () => {},
        },
        reload: {
          configurable: true,
          enumerable: true,
          writeable: true,
          // Prevents JSDOM errors from doing window.location.reload()
          value: () => {},
        },
      }
    ),
  // Prevents JSDOM errors from doing window.location = ...
  set: () => {},
})
<|endoftext|>


<commit_before>/*global define*/
/*jslint white: true*/
/**
 * "Download" panel for each element in data list panel.
 * @author Roman Sutormin <rsutormin@lbl.gov>
 * @public
 */
define (
	[
		'kbwidget',
		'bootstrap',
		'jquery',
		'narrativeConfig',
		'kbase-client-api',
		'kbase-generic-client-api',
		'base/js/namespace'
	], function(
		KBWidget,
		bootstrap,
		$,
		Config,
		kbase_client_api,
		GenericClient,
		Jupyter
	) {
    'use strict';
    return KBWidget({
        name: "kbaseNarrativeDownloadPanel",
        version: "1.0.0",
        options: {
        	token: null,
        	type: null,
        	wsId: null,
        	objId: null,
        	downloadSpecCache: null  // {'lastUpdateTime': <millisec.>, 'types': {<type>: <spec>}}
        },
        token: null,
        type: null,  // Type of workspace object to show downloaders for
        wsId: null,
        objId: null,
        loadingImage: Config.get('loading_gif'),
        wsUrl: Config.url('workspace'),
        ujsURL: Config.url('user_and_job_state'),
        shockURL: Config.url('shock'),
        exportURL: Config.url('data_import_export'),
        nmsURL: Config.url('narrative_method_store'),
        eeURL: Config.url('job_service'),
        timer: null,
        downloadSpecCache: null,    // {'lastUpdateTime': <millisec.>, 'types': {<type>: <spec>}}

        init: function(options) {
            this._super(options);
            var self = this;
            this.token = this.options.token;
            this.type = this.options.type;
            this.wsId = this.options.wsId;
            this.objId = this.options.objId;
            this.downloadSpecCache = options['downloadSpecCache'];
            var lastUpdateTime = this.downloadSpecCache['lastUpdateTime'];
            if (lastUpdateTime) {
                this.render();
            } else {
                var nms = new NarrativeMethodStore(this.nmsURL, { token: this.token });
                nms.list_categories({'load_methods': 0, 'load_apps' : 0, 'load_types' : 1},
                        $.proxy(function(data) {
                            var aTypes = data[3];
                            var types = {};
                            var count = 0;
                            for (var key in aTypes) {
                                if (aTypes[key]["loading_error"]) {
                                    console.log("Error loading type [" + key + "]: " +
                                            aTypes[key]["loading_error"]);
                                    continue;
                                }
                                types[key] = aTypes[key];
                                count++;
                            }
                            self.downloadSpecCache['types'] = types;
                            self.downloadSpecCache['lastUpdateTime'] = Date.now();
                            console.log(count + " type-specs loaded");
                            self.render();
                        }, this),
                        $.proxy(function(error) {
                            self.showError(error);
                        }, this)
                );
            }
            return this;
        },

        render: function() {
            var self = this;
    		var downloadPanel = this.$elem;

    		var $labeltd = $('').css({'white-space':'nowrap','padding':'1px'})
    		        .append('Export as:');
    		var $btnTd = $('').css({'padding':'1px'});
    		downloadPanel.append($('
').css({width:'100%'})
    		        .append('
')
    		        .append($labeltd)
    		        .append($btnTd));

    		var addDownloader = function(descr) {
    		    $btnTd.append($('<button>').addClass('kb-data-list-btn')
    		            .append(descr.name)
    		            .click(function() {
    		                $btnTd.find('.kb-data-list-btn').prop('disabled', true);
    		                self.runDownloader(self.type, self.wsId, self.objId, descr);
    		            }));
    		};

    		var downloaders = self.prepareDownloaders(self.type, self.wsId, self.objId);
    		for (var downloadPos in downloaders)
    			addDownloader(downloaders[downloadPos]);

    		$btnTd.append($('<button>').addClass('kb-data-list-btn')
                    .append('JSON')
                    .click(function() {
                    	var url = self.exportURL + '/download?' +
                    	    'ws='+encodeURIComponent(self.wsId)+
                    	    '&id='+encodeURIComponent(self.objId)+
                    		'&url='+encodeURIComponent(self.wsUrl) + '&wszip=1'+
                    		'&name=' + encodeURIComponent(self.objId + '.JSON.zip');
                    	self.downloadFile(url);
                    }));
    		$btnTd.append($('<button>').addClass('kb-data-list-cancel-btn')
    		        .append('Cancel')
    		        .click(function() {
    		            self.stopTimer();
    		            downloadPanel.empty();
    		        } ));

    		self.$statusDiv = $('
').css({'margin':'15px'});
    		self.$statusDivContent = $('
');
    		self.$statusDiv.append(self.$statusDivContent);
    		downloadPanel.append(self.$statusDiv.hide());
        },

        prepareDownloaders: function(type, wsId, objId) {
            var ret = [];
            var typeSpec = this.downloadSpecCache['types'] ?
                    this.downloadSpecCache['types'][type] : null;
            if (typeSpec && typeSpec['export_functions']) {
                for (var name in typeSpec['export_functions'])
                    if (typeSpec['export_functions'].hasOwnProperty(name))
                        ret.push({name: name, local_function:
                            typeSpec['export_functions'][name]});
            } else {
                console.log("Type [" + type + "] was skipped (no 'export_functions' block in " +
                        "type-spec).");
            }
            return ret;
        },

        getVersionTag: function() {
            var tag = Jupyter.narrative.sidePanel.$methodsWidget.currentTag;
            if (!tag) {
                tag = "release";
            }
            return tag;
        },

        runDownloader: function(type, wsId, objId, descr) {
            // descr is {name: ..., local_function: ...}
            var self = this;
            self.showMessage(' Export status: Preparing data');
            self.$statusDiv.show();
            var wsObjectName = objId + '.' + descr.name.replace(/[^a-zA-Z0-9|\.\-_]/g,'_');
            var tag = self.getVersionTag();
            var method = descr.local_function.replace('/', '.');
            var genericClient = new GenericClient(this.eeURL, {token: this.token}, null,
                    false);
            genericClient.sync_call("NarrativeJobService.run_job",
                    [{method: method, params: [{input_ref: wsId + "/" + objId}],
                        service_ver: tag}], function(data){
                var jobId = data[0];
                console.log("Running " + descr.local_function + " (tag=\"" + tag + "\"), " +
                        "job ID: " + jobId);
                self.waitForSdkJob(jobId, wsObjectName);
            },
            function(error){
                console.error(error);
                self.showError(error);
            });
        },

        waitForSdkJob: function(jobId, wsObjectName) {
            var self = this;
            var genericClient = new GenericClient(this.eeURL, { token: this.token }, null, false);
            var skipLogLines = 0;
            var lastLogLine = null;
            var timeLst = function(event) {
                genericClient.sync_call("NarrativeJobService.check_job", [jobId], function(data) {
                    var jobState = data[0];
                    genericClient.sync_call("NarrativeJobService.get_job_logs",
                            [{"job_id": jobId, "skip_lines": skipLogLines}], function(data2) {
                        var logLines = data2[0].lines;
                        for (var i = 0; i < logLines.length; i++) {
                            lastLogLine = logLines[i];
                            if (lastLogLine.is_error) {
                                console.error("Export logging: " + lastLogLine.line);
                            } else {
                                console.log("Export logging: " + lastLogLine.line);
                            }
                        }
                        skipLogLines += logLines.length;
                        var complete = jobState['finished'];
                        var error = jobState['error'];
                        if (complete) {
                            self.stopTimer();
                            if (error) {
                                console.error(error);
                                self.showError(error['message']);
                            } else {
                                console.log("Export is complete");
                                // Starting download from Shock
                                self.$statusDiv.hide();
                                self.$elem.find('.kb-data-list-btn').prop('disabled', false);
                                var result = jobState['result'];
                                self.downloadUJSResults(result[0].shock_id, self.shockURL,
                                        wsObjectName);
                            }
                        } else {
                            var status = skipLogLines == 0 ? jobState['job_state'] :
                                lastLogLine.line;
                            if (skipLogLines == 0)
                                console.log("Export status: " + status);
                            self.showMessage(' ' +
                                    'Export status: ' + status);
                        }
                    }, function(data) {
                        self.stopTimer();
                        console.log(data.error.message);
                        self.showError(data.error.message);
                    });
                }, function(data) {
                    self.stopTimer();
                    console.log(data.error.message);
                    self.showError(data.error.message);
                });
            };
            self.timer = setInterval(timeLst, 5000);
            timeLst();
        },

        downloadUJSResults: function(shockNode, remoteShockUrl, wsObjectName, unzip) {
        	var self = this;
			var elems = shockNode.split('/');
			if (elems.length > 1)
				shockNode = elems[elems.length - 1];
			elems = shockNode.split('?');
			if (elems.length > 0)
				shockNode = elems[0];
			console.log("Shock node ID: " + shockNode);
        	var shockClient = new ShockClient({url: self.shockURL, token: self.token});
        	var downloadShockNodeWithName = function(name) {
    			var url = self.exportURL + '/download?id='+shockNode+'&del=1';
    			if (unzip) {
    				url += '&unzip='+encodeURIComponent(unzip);
    			} else {
    				url += '&name='+encodeURIComponent(name);
    			}
    			if (remoteShockUrl)
    				url += '&url='+encodeURIComponent(remoteShockUrl);
    			self.downloadFile(url);
        	};
        	downloadShockNodeWithName(wsObjectName + ".zip");
        },

        downloadFile: function(url) {
        	console.log("Downloading url=" + url);
        	var hiddenIFrameID = 'hiddenDownloader';
            var iframe = document.getElementById(hiddenIFrameID);
        	if (iframe === null) {
        		iframe = document.createElement('iframe');
        		iframe.id = hiddenIFrameID;
        		iframe.style.display = 'none';
        		document.body.appendChild(iframe);
        	}
        	iframe.src = url;
        },

        showMessage: function(msg) {
        	var self = this;
            self.$statusDivContent.empty();
            self.$statusDivContent.append(msg);
        },

        showError: function(msg) {
        	var self = this;
            if (typeof msg === 'object' && msg.error) {
                msg = msg.error;
                if (typeof msg === 'object' && msg.message)
                    msg = msg.message;
            }
        	self.$statusDivContent.empty();
        	// error is final state, so reactivate!
        	self.$elem.find('.kb-data-list-btn').prop('disabled', false);
        	self.$statusDivContent.append($('').css({color:'#F44336'})
        	        .append('Error: '+msg));
        },

        stopTimer: function() {
			if (this.timer != null) {
				clearInterval(this.timer);
				this.timer = null;
				console.log("Timer was stopped");
			}
		}
    });
});
<commit_msg>Alternative download logic was added based on dynamic services.<commit_after>/*global define*/
/*jslint white: true*/
/**
 * "Download" panel for each element in data list panel.
 * @author Roman Sutormin <rsutormin@lbl.gov>
 * @public
 */
define (
	[
		'kbwidget',
		'bootstrap',
		'jquery',
		'narrativeConfig',
		'kbase-client-api',
		'kbase-generic-client-api',
		'base/js/namespace'
	], function(
		KBWidget,
		bootstrap,
		$,
		Config,
		kbase_client_api,
		GenericClient,
		Jupyter
	) {
    'use strict';
    return KBWidget({
        name: "kbaseNarrativeDownloadPanel",
        version: "1.0.0",
        options: {
        	token: null,
        	type: null,
        	wsId: null,
        	objId: null,
        	downloadSpecCache: null  // {'lastUpdateTime': <millisec.>, 'types': {<type>: <spec>}}
        },
        token: null,
        type: null,  // Type of workspace object to show downloaders for
        wsId: null,
        objId: null,
        loadingImage: Config.get('loading_gif'),
        wsUrl: Config.url('workspace'),
        ujsURL: Config.url('user_and_job_state'),
        shockURL: Config.url('shock'),
        exportURL: Config.url('data_import_export'),
        useDynamicDownloadSupport: false,
        nmsURL: Config.url('narrative_method_store'),
        eeURL: Config.url('job_service'),
        srvWizURL: Config.url('service_wizard'),
        timer: null,
        downloadSpecCache: null,    // {'lastUpdateTime': <millisec.>, 'types': {<type>: <spec>}}

        init: function(options) {
            this._super(options);
            var self = this;
            this.token = this.options.token;
            this.type = this.options.type;
            this.wsId = this.options.wsId;
            this.objId = this.options.objId;
            this.downloadSpecCache = options['downloadSpecCache'];
            var lastUpdateTime = this.downloadSpecCache['lastUpdateTime'];
            if (lastUpdateTime) {
                this.render();
            } else {
                var nms = new NarrativeMethodStore(this.nmsURL, { token: this.token });
                nms.list_categories({'load_methods': 0, 'load_apps' : 0, 'load_types' : 1},
                        $.proxy(function(data) {
                            var aTypes = data[3];
                            var types = {};
                            var count = 0;
                            for (var key in aTypes) {
                                if (aTypes[key]["loading_error"]) {
                                    console.log("Error loading type [" + key + "]: " +
                                            aTypes[key]["loading_error"]);
                                    continue;
                                }
                                types[key] = aTypes[key];
                                count++;
                            }
                            self.downloadSpecCache['types'] = types;
                            self.downloadSpecCache['lastUpdateTime'] = Date.now();
                            console.log(count + " type-specs loaded");
                            self.render();
                        }, this),
                        $.proxy(function(error) {
                            self.showError(error);
                        }, this)
                );
            }
            return this;
        },

        render: function() {
            var self = this;
    		var downloadPanel = this.$elem;

    		var $labeltd = $('').css({'white-space':'nowrap','padding':'1px'})
    		        .append('Export as:');
    		var $btnTd = $('').css({'padding':'1px'});
    		downloadPanel.append($('
').css({width:'100%'})
    		        .append('
')
    		        .append($labeltd)
    		        .append($btnTd));

    		var addDownloader = function(descr) {
    		    $btnTd.append($('<button>').addClass('kb-data-list-btn')
    		            .append(descr.name)
    		            .click(function() {
    		                $btnTd.find('.kb-data-list-btn').prop('disabled', true);
    		                self.runDownloader(self.type, self.wsId, self.objId, descr);
    		            }));
    		};

    		var downloaders = self.prepareDownloaders(self.type, self.wsId, self.objId);
    		for (var downloadPos in downloaders)
    			addDownloader(downloaders[downloadPos]);

    		$btnTd.append($('<button>').addClass('kb-data-list-btn')
                    .append('JSON')
                    .click(function() {
                    	var urlSuffix = '/download?' +
                    	    'ws='+encodeURIComponent(self.wsId)+
                    	    '&id='+encodeURIComponent(self.objId)+
                    		'&url='+encodeURIComponent(self.wsUrl) + '&wszip=1'+
                    		'&name=' + encodeURIComponent(self.objId + '.JSON.zip');
                    	self.downloadFile(urlSuffix);
                    }));
    		$btnTd.append($('<button>').addClass('kb-data-list-cancel-btn')
    		        .append('Cancel')
    		        .click(function() {
    		            self.stopTimer();
    		            downloadPanel.empty();
    		        } ));

    		self.$statusDiv = $('
').css({'margin':'15px'});
    		self.$statusDivContent = $('
');
    		self.$statusDiv.append(self.$statusDivContent);
    		downloadPanel.append(self.$statusDiv.hide());
        },

        prepareDownloaders: function(type, wsId, objId) {
            var ret = [];
            var typeSpec = this.downloadSpecCache['types'] ?
                    this.downloadSpecCache['types'][type] : null;
            if (typeSpec && typeSpec['export_functions']) {
                for (var name in typeSpec['export_functions'])
                    if (typeSpec['export_functions'].hasOwnProperty(name))
                        ret.push({name: name, local_function:
                            typeSpec['export_functions'][name]});
            } else {
                console.log("Type [" + type + "] was skipped (no 'export_functions' block in " +
                        "type-spec).");
            }
            return ret;
        },

        getVersionTag: function() {
            var tag = Jupyter.narrative.sidePanel.$methodsWidget.currentTag;
            if (!tag) {
                tag = "release";
            }
            return tag;
        },

        runDownloader: function(type, wsId, objId, descr) {
            // descr is {name: ..., local_function: ...}
            var self = this;
            self.showMessage(' Export status: Preparing data');
            self.$statusDiv.show();
            var wsObjectName = objId + '.' + descr.name.replace(/[^a-zA-Z0-9|\.\-_]/g,'_');
            var tag = self.getVersionTag();
            var method = descr.local_function.replace('/', '.');
            var genericClient = new GenericClient(this.eeURL, {token: this.token}, null,
                    false);
            genericClient.sync_call("NarrativeJobService.run_job",
                    [{method: method, params: [{input_ref: wsId + "/" + objId}],
                        service_ver: tag}], function(data){
                var jobId = data[0];
                console.log("Running " + descr.local_function + " (tag=\"" + tag + "\"), " +
                        "job ID: " + jobId);
                self.waitForSdkJob(jobId, wsObjectName);
            },
            function(error){
                console.error(error);
                self.showError(error);
            });
        },

        waitForSdkJob: function(jobId, wsObjectName) {
            var self = this;
            var genericClient = new GenericClient(this.eeURL, { token: this.token }, null, false);
            var skipLogLines = 0;
            var lastLogLine = null;
            var timeLst = function(event) {
                genericClient.sync_call("NarrativeJobService.check_job", [jobId], function(data) {
                    var jobState = data[0];
                    genericClient.sync_call("NarrativeJobService.get_job_logs",
                            [{"job_id": jobId, "skip_lines": skipLogLines}], function(data2) {
                        var logLines = data2[0].lines;
                        for (var i = 0; i < logLines.length; i++) {
                            lastLogLine = logLines[i];
                            if (lastLogLine.is_error) {
                                console.error("Export logging: " + lastLogLine.line);
                            } else {
                                console.log("Export logging: " + lastLogLine.line);
                            }
                        }
                        skipLogLines += logLines.length;
                        var complete = jobState['finished'];
                        var error = jobState['error'];
                        if (complete) {
                            self.stopTimer();
                            if (error) {
                                console.error(error);
                                self.showError(error['message']);
                            } else {
                                console.log("Export is complete");
                                // Starting download from Shock
                                self.$statusDiv.hide();
                                self.$elem.find('.kb-data-list-btn').prop('disabled', false);
                                var result = jobState['result'];
                                self.downloadUJSResults(result[0].shock_id, self.shockURL,
                                        wsObjectName);
                            }
                        } else {
                            var status = skipLogLines == 0 ? jobState['job_state'] :
                                lastLogLine.line;
                            if (skipLogLines == 0)
                                console.log("Export status: " + status);
                            self.showMessage(' ' +
                                    'Export status: ' + status);
                        }
                    }, function(data) {
                        self.stopTimer();
                        console.log(data.error.message);
                        self.showError(data.error.message);
                    });
                }, function(data) {
                    self.stopTimer();
                    console.log(data.error.message);
                    self.showError(data.error.message);
                });
            };
            self.timer = setInterval(timeLst, 5000);
            timeLst();
        },

        downloadUJSResults: function(shockNode, remoteShockUrl, wsObjectName, unzip) {
        	var self = this;
			var elems = shockNode.split('/');
			if (elems.length > 1)
				shockNode = elems[elems.length - 1];
			elems = shockNode.split('?');
			if (elems.length > 0)
				shockNode = elems[0];
			console.log("Shock node ID: " + shockNode);
        	var shockClient = new ShockClient({url: self.shockURL, token: self.token});
        	var downloadShockNodeWithName = function(name) {
    			var urlSuffix = '/download?id='+shockNode+'&del=1';
    			if (unzip) {
    			    urlSuffix += '&unzip='+encodeURIComponent(unzip);
    			} else {
    			    urlSuffix += '&name='+encodeURIComponent(name);
    			}
    			if (remoteShockUrl)
    			    urlSuffix += '&url='+encodeURIComponent(remoteShockUrl);
    			self.downloadFile(urlSuffix);
        	};
        	downloadShockNodeWithName(wsObjectName + ".zip");
        },

        downloadFile: function(urlSuffix) {
            var self = this;
            if (self.useDynamicDownloadSupport) {
                var genericClient = new GenericClient(self.srvWizURL, {token: self.token}, null,
                        false);
                genericClient.sync_call("ServiceWizard.get_service_status",
                        [{module_name: 'NarrativeDownloadSupport', version: 'dev'}], function(data) {
                    var urlPrefix = data[0]['url'];
                    self.downloadFileInner(urlPrefix + urlSuffix);
                },
                function(error){
                    console.error(error);
                    self.showError(error);
                });
            } else {
                self.downloadFileInner(self.exportURL + urlSuffix);
            }
        },

        downloadFileInner: function(url) {
        	console.log("Downloading url=" + url);
        	var hiddenIFrameID = 'hiddenDownloader';
            var iframe = document.getElementById(hiddenIFrameID);
        	if (iframe === null) {
        		iframe = document.createElement('iframe');
        		iframe.id = hiddenIFrameID;
        		iframe.style.display = 'none';
        		document.body.appendChild(iframe);
        	}
        	iframe.src = url;
        },

        showMessage: function(msg) {
        	var self = this;
            self.$statusDivContent.empty();
            self.$statusDivContent.append(msg);
        },

        showError: function(msg) {
        	var self = this;
            if (typeof msg === 'object' && msg.error) {
                msg = msg.error;
                if (typeof msg === 'object' && msg.message)
                    msg = msg.message;
            }
        	self.$statusDivContent.empty();
        	// error is final state, so reactivate!
        	self.$elem.find('.kb-data-list-btn').prop('disabled', false);
        	self.$statusDivContent.append($('').css({color:'#F44336'})
        	        .append('Error: '+msg));
        },

        stopTimer: function() {
			if (this.timer != null) {
				clearInterval(this.timer);
				this.timer = null;
				console.log("Timer was stopped");
			}
		}
    });
});
<|endoftext|>


<commit_before>function Muppet(age, hobby) {
  this.age = age;
  this.hobby = hobby;

  this.answerNanny = function(){
	return "Everything's cool!";
  }
}

function SwedishChef(age, hobby, mood) {
  Muppet.call(this, age, hobby);
  this.mood = mood;

  this.cook = function() {
    return "Mmmm soup!";
  }
}

SwedishChef.prototype = new Muppet();

describe("About inheritance", function() {
  beforeEach(function(){
    this.muppet = new Muppet(2, "coding");
  	this.swedishChef = new SwedishChef(2, "cooking", "chillin");
  });

  it("should be able to call a method on the derived object", function() {
    expect(this.swedishChef.cook()).toEqual("Mmmm soup!");
  });

  it("should be able to call a method on the base object", function() {
    expect(this.swedishChef.answerNanny()).toEqual("Everything's cool!");
  });

  it("should set constructor parameters on the base object", function() {
    expect(this.swedishChef.age).toEqual(FILL_ME_IN);
    expect(this.swedishChef.hobby).toEqual(FILL_ME_IN);
  });

  it("should set constructor parameters on the derived object", function() {
    expect(this.swedishChef.mood).toEqual(FILL_ME_IN);
  });
});

// http://javascript.crockford.com/prototypal.html
Object.prototype.beget = function () {
  function F() {}
  F.prototype = this;
  return new F();
}

function Gonzo(age, hobby, trick) {
  Muppet.call(this, age, hobby);
  this.trick = trick;

  this.doTrick = function() {
    return this.trick;
  }
}

//no longer need to call the Muppet (base type) constructor
Gonzo.prototype = Muppet.prototype.beget();
//note: if you're wondering how this line affects the below tests, the answer is that it doesn't.
//however, it does do something interesting -- it makes this work:
//  var g = new Gonzo(...);
//  g instanceOf Muppet //true

describe("About Crockford's inheritance improvement", function() {
  beforeEach(function(){
    this.gonzo = new Gonzo(3, "daredevil performer", "eat a tire");
  });

  it("should be able to call a method on the derived object", function() {
    expect(this.gonzo.doTrick()).toEqual(FILL_ME_IN);
  });

  it("should be able to call a method on the base object", function() {
    expect(this.gonzo.answerNanny()).toEqual(FILL_ME_IN);
  });

  it("should set constructor parameters on the base object", function() {
    expect(this.gonzo.age).toEqual(FILL_ME_IN);
    expect(this.gonzo.hobby).toEqual(FILL_ME_IN);
  });

  it("should set constructor parameters on the derived object", function() {
    expect(this.gonzo.trick).toEqual(FILL_ME_IN);
  });
});
<commit_msg>sets constructor params on the base object<commit_after>function Muppet(age, hobby) {
  this.age = age;
  this.hobby = hobby;

  this.answerNanny = function(){
	return "Everything's cool!";
  }
}

function SwedishChef(age, hobby, mood) {
  Muppet.call(this, age, hobby);
  this.mood = mood;

  this.cook = function() {
    return "Mmmm soup!";
  }
}

SwedishChef.prototype = new Muppet();

describe("About inheritance", function() {
  beforeEach(function(){
    this.muppet = new Muppet(2, "coding");
  	this.swedishChef = new SwedishChef(2, "cooking", "chillin");
  });

  it("should be able to call a method on the derived object", function() {
    expect(this.swedishChef.cook()).toEqual("Mmmm soup!");
  });

  it("should be able to call a method on the base object", function() {
    expect(this.swedishChef.answerNanny()).toEqual("Everything's cool!");
  });

  it("should set constructor parameters on the base object", function() {
    expect(this.swedishChef.age).toEqual(2);
    expect(this.swedishChef.hobby).toEqual("cooking");
  });

  it("should set constructor parameters on the derived object", function() {
    expect(this.swedishChef.mood).toEqual(FILL_ME_IN);
  });
});

// http://javascript.crockford.com/prototypal.html
Object.prototype.beget = function () {
  function F() {}
  F.prototype = this;
  return new F();
}

function Gonzo(age, hobby, trick) {
  Muppet.call(this, age, hobby);
  this.trick = trick;

  this.doTrick = function() {
    return this.trick;
  }
}

//no longer need to call the Muppet (base type) constructor
Gonzo.prototype = Muppet.prototype.beget();
//note: if you're wondering how this line affects the below tests, the answer is that it doesn't.
//however, it does do something interesting -- it makes this work:
//  var g = new Gonzo(...);
//  g instanceOf Muppet //true

describe("About Crockford's inheritance improvement", function() {
  beforeEach(function(){
    this.gonzo = new Gonzo(3, "daredevil performer", "eat a tire");
  });

  it("should be able to call a method on the derived object", function() {
    expect(this.gonzo.doTrick()).toEqual(FILL_ME_IN);
  });

  it("should be able to call a method on the base object", function() {
    expect(this.gonzo.answerNanny()).toEqual(FILL_ME_IN);
  });

  it("should set constructor parameters on the base object", function() {
    expect(this.gonzo.age).toEqual(FILL_ME_IN);
    expect(this.gonzo.hobby).toEqual(FILL_ME_IN);
  });

  it("should set constructor parameters on the derived object", function() {
    expect(this.gonzo.trick).toEqual(FILL_ME_IN);
  });
});
<|endoftext|>


<commit_before>function Muppet(age, hobby) {
  this.age   = age;
  this.hobby = hobby;

  this.answerNanny = function(){
	return "Everything's cool!";
  }
}

function SwedishChef(age, hobby, mood) {
  Muppet.call(this, age, hobby);
  this.mood = mood;

  this.cook = function() {
    return "Mmmm soup!";
  }
}

SwedishChef.prototype = new Muppet();

describe("About inheritance", function() {
  beforeEach(function(){
    this.muppet      = new Muppet(2, "coding");
  	this.swedishChef = new SwedishChef(2, "cooking", "chillin");
  });

  it("should be able to call a method on the derived object", function() {
    expect(this.swedishChef.cook()).toEqual(FILL_ME_IN);
  });

  it("should be able to call a method on the base object", function() {
    expect(this.swedishChef.answerNanny()).toEqual(FILL_ME_IN);
  });

  it("should set constructor parameters on the base object", function() {
    expect(this.swedishChef.age).toEqual(FILL_ME_IN);
    expect(this.swedishChef.hobby).toEqual(FILL_ME_IN);
  });

  it("should set constructor parameters on the derived object", function() {
    expect(this.swedishChef.mood).toEqual(FILL_ME_IN);
  });
});

// http://javascript.crockford.com/prototypal.html
Object.prototype.beget = function () {
  function F() {}
  F.prototype = this;
  return new F();
}

function Gonzo(age, hobby, trick) {
  Muppet.call(this, age, hobby);
  this.trick = trick;

  this.doTrick = function() {
    return this.trick;
  }
}

// no longer need to call the Muppet (base type) constructor
Gonzo.prototype = Muppet.prototype.beget();
// note: if you're wondering how this line affects the below tests, the answer is that it doesn't.
// however, it does do something interesting -- it makes this work:
// var g = new Gonzo(...);
// g instanceOf Muppet // true

describe("About Crockford's inheritance improvement", function() {
  beforeEach(function(){
    this.gonzo = new Gonzo(3, "daredevil performer", "eat a tire");
  });

  it("should be able to call a method on the derived object", function() {
    expect(this.gonzo.doTrick()).toEqual(FILL_ME_IN);
  });

  it("should be able to call a method on the base object", function() {
    expect(this.gonzo.answerNanny()).toEqual(FILL_ME_IN);
  });

  it("should set constructor parameters on the base object", function() {
    expect(this.gonzo.age).toEqual(FILL_ME_IN);
    expect(this.gonzo.hobby).toEqual(FILL_ME_IN);
  });

  it("should set constructor parameters on the derived object", function() {
    expect(this.gonzo.trick).toEqual(FILL_ME_IN);
  });
});
<commit_msg>Completed AboutInheritance<commit_after>function Muppet(age, hobby) {
  this.age   = age;
  this.hobby = hobby;

  this.answerNanny = function(){
	return "Everything's cool!";
  }
}

function SwedishChef(age, hobby, mood) {
  Muppet.call(this, age, hobby);
  this.mood = mood;

  this.cook = function() {
    return "Mmmm soup!";
  }
}

SwedishChef.prototype = new Muppet();

describe("About inheritance", function() {
  beforeEach(function(){
    this.muppet      = new Muppet(2, "coding");
  	this.swedishChef = new SwedishChef(2, "cooking", "chillin");
  });

  it("should be able to call a method on the derived object", function() {
    expect(this.swedishChef.cook()).toEqual('Mmmm soup!');
  });

  it("should be able to call a method on the base object", function() {
    expect(this.swedishChef.answerNanny()).toEqual("Everything's cool!");
  });

  it("should set constructor parameters on the base object", function() {
    expect(this.swedishChef.age).toEqual(2);
    expect(this.swedishChef.hobby).toEqual("cooking");
  });

  it("should set constructor parameters on the derived object", function() {
    expect(this.swedishChef.mood).toEqual("chillin");
  });
});

// http://javascript.crockford.com/prototypal.html
Object.prototype.beget = function () {
  function F() {}
  F.prototype = this;
  return new F();
}

function Gonzo(age, hobby, trick) {
  Muppet.call(this, age, hobby);
  this.trick = trick;

  this.doTrick = function() {
    return this.trick;
  }
}

// no longer need to call the Muppet (base type) constructor
Gonzo.prototype = Muppet.prototype.beget();
// note: if you're wondering how this line affects the below tests, the answer is that it doesn't.
// however, it does do something interesting -- it makes this work:
// var g = new Gonzo(...);
// g instanceOf Muppet // true

describe("About Crockford's inheritance improvement", function() {
  beforeEach(function(){
    this.gonzo = new Gonzo(3, "daredevil performer", "eat a tire");
  });

  it("should be able to call a method on the derived object", function() {
    expect(this.gonzo.doTrick()).toEqual(FILL_ME_IN);
  });

  it("should be able to call a method on the base object", function() {
    expect(this.gonzo.answerNanny()).toEqual(FILL_ME_IN);
  });

  it("should set constructor parameters on the base object", function() {
    expect(this.gonzo.age).toEqual(FILL_ME_IN);
    expect(this.gonzo.hobby).toEqual(FILL_ME_IN);
  });

  it("should set constructor parameters on the derived object", function() {
    expect(this.gonzo.trick).toEqual(FILL_ME_IN);
  });
});
<|endoftext|>


<commit_before>import React from 'react';

import List from 'src/List';
import ListRow from 'src/ListRow';

import Button from 'src/Button';
import TextLabel from 'src/TextLabel';

import DebugBox from '../DebugBox';

function NormalList() {
    return (
        <DebugBox width="30rem">
            <List variant="normal" title="List title" desc="Help text here">
                <ListRow>
                    <TextLabel icon="tickets" basic="Hello World" />
                </ListRow>
                <ListRow highlight>
                    <TextLabel icon="tickets" basic="Highlighted row" />
                </ListRow>
                <ListRow>
                    <TextLabel icon="tickets" basic="Row 3" />
                </ListRow>
                <ListRow>
                    <Button
                        icon="add"
                        basic="Add row" />
                </ListRow>
            </List>
        </DebugBox>
    );
}

export default NormalList;
<commit_msg>Update example for revised <ListRow><commit_after>import React from 'react';

import List from 'src/List';
import ListRow from 'src/ListRow';

import Button from 'src/Button';
import TextLabel from 'src/TextLabel';

import DebugBox from '../DebugBox';

function NormalList() {
    return (
        <DebugBox width="30rem">
            <List variant="normal" title="List title" desc="Help text here">
                <ListRow>
                    <TextLabel icon="tickets" basic="Hello World" />
                </ListRow>

                <ListRow
                    desc="Row help message"
                    status="error"
                    errorMsg="Row error message">
                    <TextLabel
                        icon="tickets"
                        basic="Hello World"
                        aside="Component aside" />
                </ListRow>

                <ListRow highlight>
                    <TextLabel icon="tickets" basic="Highlighted row" />
                </ListRow>
                <ListRow>
                    <TextLabel icon="tickets" basic="Row 3" />
                </ListRow>
                <ListRow>
                    <Button
                        icon="add"
                        basic="Add row" />
                </ListRow>
            </List>
        </DebugBox>
    );
}

export default NormalList;
<|endoftext|>


<commit_before>/*
 Copyright (C) 2005 SKYRIX Software AG

 This file is part of OpenGroupware.org.

 OGo is free software; you can redistribute it and/or modify it under
 the terms of the GNU Lesser General Public License as published by the
 Free Software Foundation; either version 2, or (at your option) any
 later version.

 OGo is distributed in the hope that it will be useful, but WITHOUT ANY
 WARRANTY; without even the implied warranty of MERCHANTABILITY or
 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
 License for more details.

 You should have received a copy of the GNU Lesser General Public
 License along with OGo; see the file COPYING.  If not, write to the
 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
 02111-1307, USA.
*/

// var cuicui = '';

function uixEarlierDate(date1, date2) {
  // can this be done in a sane way?
//   cuicui = 'year';
  if (date1.getYear()  < date2.getYear()) return date1;
  if (date1.getYear()  > date2.getYear()) return date2;
  // same year
//   cuicui += '/month';
  if (date1.getMonth() < date2.getMonth()) return date1;
  if (date1.getMonth() > date2.getMonth()) return date2;
//   // same month
//   cuicui += '/date';
  if (date1.getDate() < date2.getDate()) return date1;
  if (date1.getDate() > date2.getDate()) return date2;
  // same day
  return null;
}

function validateAptEditor() {
  var e, startdate, enddate, tmpdate;

  e = document.getElementById('summary');
  if (e.value.length == 0) {
    if (!confirm(labels.validate_notitle))
      return false;
  }

  e = document.getElementById('startTime_date');
  if (e.value.length != 10) {
    alert(labels.validate_invalid_startdate);
    return false;
  }
  startdate = e.calendar.prs_date(e.value);
  if (startdate == null) {
    alert(labels.validate_invalid_startdate);
    return false;
  }

  e = document.getElementById('endTime_date');
  if (e.value.length != 10) {
    alert(labels.validate_invalid_enddate);
    return false;
  }
  enddate = e.calendar.prs_date(e.value);
  if (enddate == null) {
    alert(labels.validate_invalid_enddate);
    return false;
  }
//   cuicui = '';
  tmpdate = uixEarlierDate(startdate, enddate);
  if (tmpdate == enddate) {
//     window.alert(cuicui);
    alert(labels.validate_endbeforestart);
    return false;
  }
  else if (tmpdate == null /* means: same date */) {
    // TODO: check time
    var start, end;

    start = parseInt(document.forms[0]['startTime_time_hour'].value);
    end = parseInt(document.forms[0]['endTime_time_hour'].value);

    if (start > end) {
      alert(labels.validate_endbeforestart);
      return false;
    }
    else if (start == end) {
      start = parseInt(document.forms[0]['startTime_time_minute'].value);
      end = parseInt(document.forms[0]['endTime_time_minute'].value);
      if (start > end) {
	alert(labels.validate_endbeforestart);
	return false;
      }
    }
  }

  return true;
}

function submitMeeting(thisForm) {
  var action = document.getElementById('jsaction');
  action.setAttribute("name", "save:method");
  action.setAttribute("value", "save");

  if (validateAptEditor()) {
    thisForm.submit();
    window.opener.setTimeout('refreshAppointments();', 200);
    window.close();
  }
}

function toggleDetails() {
  var div = $("details");

  var buttonsDiv = $("buttons");
  var wHeight = 0;
  if (!window._fullHeight) {
    var minHeight = (buttonsDiv.offsetTop + 2 * buttonsDiv.clientHeight);
    window._fullHeight = minHeight + div.clientHeight;
    window._hiddenHeight = minHeight;
  }

  if (div.style.visibility) {
    div.style.visibility = null;
    buttonsDiv.top = (window._hiddenHeight + buttonsDiv.clientHeight) + 'px;';
    window.resizeTo(document.body.clientWidth, window._hiddenHeight);
    $("detailsButton").innerHTML = labels["Show Details"];
  } else {
    div.style.visibility = 'visible;';
    buttonsDiv.top = null;
    window.resizeTo(document.body.clientWidth, window._fullHeight);
    $("detailsButton").innerHTML = labels["Hide Details"];
  }

  return false;
}

function toggleCycleVisibility(node, className, hiddenValue) {
  var containers = document.getElementsByClassName(className);
  var newVisibility = ((node.value == hiddenValue) ? null : 'visible;');
  for (var i = 0; i < containers.length; i++)
    containers[i].style.visibility = newVisibility;
}
<commit_msg> <commit_after>/*
 Copyright (C) 2005 SKYRIX Software AG

 This file is part of OpenGroupware.org.

 OGo is free software; you can redistribute it and/or modify it under
 the terms of the GNU Lesser General Public License as published by the
 Free Software Foundation; either version 2, or (at your option) any
 later version.

 OGo is distributed in the hope that it will be useful, but WITHOUT ANY
 WARRANTY; without even the implied warranty of MERCHANTABILITY or
 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
 License for more details.

 You should have received a copy of the GNU Lesser General Public
 License along with OGo; see the file COPYING.  If not, write to the
 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
 02111-1307, USA.
*/

// var cuicui = '';

function uixEarlierDate(date1, date2) {
  // can this be done in a sane way?
//   cuicui = 'year';
  if (date1.getYear()  < date2.getYear()) return date1;
  if (date1.getYear()  > date2.getYear()) return date2;
  // same year
//   cuicui += '/month';
  if (date1.getMonth() < date2.getMonth()) return date1;
  if (date1.getMonth() > date2.getMonth()) return date2;
//   // same month
//   cuicui += '/date';
  if (date1.getDate() < date2.getDate()) return date1;
  if (date1.getDate() > date2.getDate()) return date2;
  // same day
  return null;
}

function validateAptEditor() {
  var e, startdate, enddate, tmpdate;

  e = document.getElementById('summary');
  if (e.value.length == 0) {
    if (!confirm(labels.validate_notitle))
      return false;
  }

  e = document.getElementById('startTime_date');
  if (e.value.length != 10) {
    alert(labels.validate_invalid_startdate);
    return false;
  }
  startdate = e.calendar.prs_date(e.value);
  if (startdate == null) {
    alert(labels.validate_invalid_startdate);
    return false;
  }

  e = document.getElementById('endTime_date');
  if (e.value.length != 10) {
    alert(labels.validate_invalid_enddate);
    return false;
  }
  enddate = e.calendar.prs_date(e.value);
  if (enddate == null) {
    alert(labels.validate_invalid_enddate);
    return false;
  }
//   cuicui = '';
  tmpdate = uixEarlierDate(startdate, enddate);
  if (tmpdate == enddate) {
//     window.alert(cuicui);
    alert(labels.validate_endbeforestart);
    return false;
  }
  else if (tmpdate == null /* means: same date */) {
    // TODO: check time
    var start, end;

    start = parseInt(document.forms[0]['startTime_time_hour'].value);
    end = parseInt(document.forms[0]['endTime_time_hour'].value);

    if (start > end) {
      alert(labels.validate_endbeforestart);
      return false;
    }
    else if (start == end) {
      start = parseInt(document.forms[0]['startTime_time_minute'].value);
      end = parseInt(document.forms[0]['endTime_time_minute'].value);
      if (start > end) {
	alert(labels.validate_endbeforestart);
	return false;
      }
    }
  }

  return true;
}

function submitMeeting(thisForm) {
  var action = document.getElementById('jsaction');
  action.setAttribute("name", "save:method");
  action.setAttribute("value", "save");

  window.opener.log ("form: " + thisForm);

  window.opener.log ("validating...");
  if (validateAptEditor()) {
    window.opener.log ("submitting");
    thisForm.submit();
    window.opener.log ("setting timeout...");
    window.opener.setTimeout('refreshAppointments();', 200);
    window.opener.log ("we close...");
    window.close();
    window.opener.log ("closed");
  }
}

function toggleDetails() {
  var div = $("details");

  var buttonsDiv = $("buttons");
  var wHeight = 0;
  if (!window._fullHeight) {
    var minHeight = (buttonsDiv.offsetTop + 2 * buttonsDiv.clientHeight);
    window._fullHeight = minHeight + div.clientHeight;
    window._hiddenHeight = minHeight;
  }

  if (div.style.visibility) {
    div.style.visibility = null;
    buttonsDiv.top = (window._hiddenHeight + buttonsDiv.clientHeight) + 'px;';
    window.resizeTo(document.body.clientWidth, window._hiddenHeight);
    $("detailsButton").innerHTML = labels["Show Details"];
  } else {
    div.style.visibility = 'visible;';
    buttonsDiv.top = null;
    window.resizeTo(document.body.clientWidth, window._fullHeight);
    $("detailsButton").innerHTML = labels["Hide Details"];
  }

  return false;
}

function toggleCycleVisibility(node, className, hiddenValue) {
  var containers = document.getElementsByClassName(className);
  var newVisibility = ((node.value == hiddenValue) ? null : 'visible;');
  for (var i = 0; i < containers.length; i++)
    containers[i].style.visibility = newVisibility;
}
<|endoftext|>