fix: migrate remaining eslint-config-edx (#31760)
* fix: migrate remaining eslint-config-edx * refactor: updated eslint rules according to eslint-config-edx-es5 * refactor: add custom rules to suppress unnecessary eslint issues * refactor: add custom rules to internal eslint configs * fix: fix all indentation issues * chore: update lock file
This commit is contained in:
committed by
GitHub
parent
9cdbe72b82
commit
5549db4d80
@@ -1,11 +1,18 @@
|
||||
module.exports = {
|
||||
extends: '@edx/eslint-config',
|
||||
root: true,
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
webpack: {
|
||||
config: 'webpack.dev.config.js',
|
||||
},
|
||||
extends: '@edx/eslint-config',
|
||||
root: true,
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
webpack: {
|
||||
config: 'webpack.dev.config.js',
|
||||
},
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
indent: ['error', 4],
|
||||
'import/extensions': 'off',
|
||||
'import/no-unresolved': 'off',
|
||||
'react/jsx-indent': 'off',
|
||||
'react/jsx-indent-props': 'off',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -5,110 +5,110 @@ import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
const RightIcon = (
|
||||
<Icon
|
||||
className={['fa', 'fa-arrow-right']}
|
||||
screenReaderText={gettext('View child items')}
|
||||
/>
|
||||
<Icon
|
||||
className={['fa', 'fa-arrow-right']}
|
||||
screenReaderText={gettext('View child items')}
|
||||
/>
|
||||
);
|
||||
|
||||
const UpIcon = (
|
||||
<Icon
|
||||
className={['fa', 'fa-arrow-up']}
|
||||
screenReaderText={gettext('Navigate up')}
|
||||
/>
|
||||
<Icon
|
||||
className={['fa', 'fa-arrow-up']}
|
||||
screenReaderText={gettext('Navigate up')}
|
||||
/>
|
||||
);
|
||||
|
||||
const BLOCK_TYPE_NAME = {
|
||||
course: 'Course',
|
||||
chapter: 'Section',
|
||||
sequential: 'Sub-section',
|
||||
vertical: 'Unit',
|
||||
course: 'Course',
|
||||
chapter: 'Section',
|
||||
sequential: 'Sub-section',
|
||||
vertical: 'Unit',
|
||||
};
|
||||
|
||||
const BlockType = PropTypes.shape({
|
||||
children: PropTypes.array,
|
||||
display_name: PropTypes.string.isRequired,
|
||||
id: PropTypes.string.isRequired,
|
||||
parent: PropTypes.string,
|
||||
type: PropTypes.string.isRequired,
|
||||
children: PropTypes.array,
|
||||
display_name: PropTypes.string.isRequired,
|
||||
id: PropTypes.string.isRequired,
|
||||
parent: PropTypes.string,
|
||||
type: PropTypes.string.isRequired,
|
||||
});
|
||||
|
||||
export const BlockList = ({ blocks, selectedBlock, onSelectBlock, onChangeRoot }) => (
|
||||
<ul className="block-list">
|
||||
{blocks.map(block => (
|
||||
<li
|
||||
key={block.id}
|
||||
className={classNames(`block-type-${block.type}`, { selected: block.id === selectedBlock })}
|
||||
>
|
||||
<ul className="block-list">
|
||||
{blocks.map(block => (
|
||||
<li
|
||||
key={block.id}
|
||||
className={classNames(`block-type-${block.type}`, { selected: block.id === selectedBlock })}
|
||||
>
|
||||
<Button
|
||||
className={['block-name']}
|
||||
onClick={() => onSelectBlock(block.id)}
|
||||
label={block.display_name}
|
||||
/>
|
||||
{block.children &&
|
||||
<Button
|
||||
className={['block-name']}
|
||||
onClick={() => onSelectBlock(block.id)}
|
||||
label={block.display_name}
|
||||
onClick={() => onChangeRoot(block.id)}
|
||||
label={RightIcon}
|
||||
/>
|
||||
{block.children &&
|
||||
<Button
|
||||
onClick={() => onChangeRoot(block.id)}
|
||||
label={RightIcon}
|
||||
/>
|
||||
}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
|
||||
BlockList.propTypes = {
|
||||
blocks: PropTypes.arrayOf(BlockType),
|
||||
selectedBlock: PropTypes.string,
|
||||
onSelectBlock: PropTypes.func.isRequired,
|
||||
onChangeRoot: PropTypes.func.isRequired,
|
||||
blocks: PropTypes.arrayOf(BlockType),
|
||||
selectedBlock: PropTypes.string,
|
||||
onSelectBlock: PropTypes.func.isRequired,
|
||||
onChangeRoot: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
BlockList.defaultProps = {
|
||||
blocks: null,
|
||||
selectedBlock: null,
|
||||
blocks: null,
|
||||
selectedBlock: null,
|
||||
};
|
||||
|
||||
|
||||
export const BlockBrowser = ({ blocks, selectedBlock, onSelectBlock, onChangeRoot, className }) =>
|
||||
!!blocks && (
|
||||
<div className={classNames('block-browser', className)}>
|
||||
<div className="header">
|
||||
<Button
|
||||
disabled={!blocks.parent}
|
||||
onClick={() => blocks.parent && onChangeRoot(blocks.parent)}
|
||||
label={UpIcon}
|
||||
/>
|
||||
<span className="title">
|
||||
{gettext('Browsing')} {gettext(BLOCK_TYPE_NAME[blocks.type])} "
|
||||
<a
|
||||
href="#_"
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
onSelectBlock(blocks.id);
|
||||
}}
|
||||
title={`${gettext('Select')} ${gettext(BLOCK_TYPE_NAME[blocks.type])}`}
|
||||
>
|
||||
{blocks.display_name}
|
||||
</a>":
|
||||
</span>
|
||||
</div>
|
||||
<BlockList
|
||||
blocks={blocks.children}
|
||||
selectedBlock={selectedBlock}
|
||||
onSelectBlock={onSelectBlock}
|
||||
onChangeRoot={onChangeRoot}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
!!blocks && (
|
||||
<div className={classNames('block-browser', className)}>
|
||||
<div className="header">
|
||||
<Button
|
||||
disabled={!blocks.parent}
|
||||
onClick={() => blocks.parent && onChangeRoot(blocks.parent)}
|
||||
label={UpIcon}
|
||||
/>
|
||||
<span className="title">
|
||||
{gettext('Browsing')} {gettext(BLOCK_TYPE_NAME[blocks.type])} "
|
||||
<a
|
||||
href="#_"
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
onSelectBlock(blocks.id);
|
||||
}}
|
||||
title={`${gettext('Select')} ${gettext(BLOCK_TYPE_NAME[blocks.type])}`}
|
||||
>
|
||||
{blocks.display_name}
|
||||
</a>":
|
||||
</span>
|
||||
</div>
|
||||
<BlockList
|
||||
blocks={blocks.children}
|
||||
selectedBlock={selectedBlock}
|
||||
onSelectBlock={onSelectBlock}
|
||||
onChangeRoot={onChangeRoot}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
BlockBrowser.propTypes = {
|
||||
blocks: BlockType,
|
||||
selectedBlock: PropTypes.string,
|
||||
onSelectBlock: PropTypes.func.isRequired,
|
||||
onChangeRoot: PropTypes.func.isRequired,
|
||||
blocks: BlockType,
|
||||
selectedBlock: PropTypes.string,
|
||||
onSelectBlock: PropTypes.func.isRequired,
|
||||
onChangeRoot: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
BlockBrowser.defaultProps = {
|
||||
blocks: null,
|
||||
selectedBlock: null,
|
||||
blocks: null,
|
||||
selectedBlock: null,
|
||||
};
|
||||
|
||||
@@ -5,45 +5,45 @@ import { BlockBrowser, BlockList } from './BlockBrowser';
|
||||
import testBlockTree from './test-block-tree.json';
|
||||
|
||||
describe('BlockList component', () => {
|
||||
test('render with basic parameters', () => {
|
||||
const component = renderer.create(
|
||||
<BlockList
|
||||
blocks={testBlockTree.children}
|
||||
onSelectBlock={jest.fn()}
|
||||
selectedBlock={null}
|
||||
onChangeRoot={jest.fn()}
|
||||
/>,
|
||||
);
|
||||
const tree = component.toJSON();
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
test('render with basic parameters', () => {
|
||||
const component = renderer.create(
|
||||
<BlockList
|
||||
blocks={testBlockTree.children}
|
||||
onSelectBlock={jest.fn()}
|
||||
selectedBlock={null}
|
||||
onChangeRoot={jest.fn()}
|
||||
/>,
|
||||
);
|
||||
const tree = component.toJSON();
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('BlockBrowser component', () => {
|
||||
test('render with basic parameters', () => {
|
||||
const component = renderer.create(
|
||||
<BlockBrowser
|
||||
blocks={testBlockTree}
|
||||
onSelectBlock={jest.fn()}
|
||||
selectedBlock={null}
|
||||
onChangeRoot={jest.fn()}
|
||||
/>,
|
||||
);
|
||||
const tree = component.toJSON();
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
test('render with basic parameters', () => {
|
||||
const component = renderer.create(
|
||||
<BlockBrowser
|
||||
blocks={testBlockTree}
|
||||
onSelectBlock={jest.fn()}
|
||||
selectedBlock={null}
|
||||
onChangeRoot={jest.fn()}
|
||||
/>,
|
||||
);
|
||||
const tree = component.toJSON();
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('render with custom classname', () => {
|
||||
const component = renderer.create(
|
||||
<BlockBrowser
|
||||
blocks={testBlockTree}
|
||||
className="some-class"
|
||||
onSelectBlock={jest.fn()}
|
||||
selectedBlock={null}
|
||||
onChangeRoot={jest.fn()}
|
||||
/>,
|
||||
);
|
||||
const tree = component.toJSON();
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
test('render with custom classname', () => {
|
||||
const component = renderer.create(
|
||||
<BlockBrowser
|
||||
blocks={testBlockTree}
|
||||
className="some-class"
|
||||
onSelectBlock={jest.fn()}
|
||||
selectedBlock={null}
|
||||
onChangeRoot={jest.fn()}
|
||||
/>,
|
||||
);
|
||||
const tree = component.toJSON();
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,19 +4,19 @@ import { getActiveBlockTree } from '../../data/selectors/index';
|
||||
import { BlockBrowser } from './BlockBrowser';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
blocks: getActiveBlockTree(state),
|
||||
selectedBlock: state.selectedBlock,
|
||||
blocks: getActiveBlockTree(state),
|
||||
selectedBlock: state.selectedBlock,
|
||||
});
|
||||
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onChangeRoot: blockId => dispatch(changeRoot(blockId)),
|
||||
onChangeRoot: blockId => dispatch(changeRoot(blockId)),
|
||||
});
|
||||
|
||||
|
||||
const BlockBrowserContainer = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(BlockBrowser);
|
||||
|
||||
export default BlockBrowserContainer;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
export default {
|
||||
fetch: {
|
||||
SUCCESS: 'FETCH_COURSE_BLOCKS_SUCCESS',
|
||||
FAILURE: 'FETCH_COURSE_BLOCKS_FAILURE',
|
||||
},
|
||||
SELECT_BLOCK: 'SELECT_BLOCK',
|
||||
CHANGE_ROOT: 'CHANGE_ROOT',
|
||||
fetch: {
|
||||
SUCCESS: 'FETCH_COURSE_BLOCKS_SUCCESS',
|
||||
FAILURE: 'FETCH_COURSE_BLOCKS_FAILURE',
|
||||
},
|
||||
SELECT_BLOCK: 'SELECT_BLOCK',
|
||||
CHANGE_ROOT: 'CHANGE_ROOT',
|
||||
};
|
||||
|
||||
@@ -2,37 +2,37 @@ import { getCourseBlocks } from '../api/client';
|
||||
import courseBlocksActions from './constants';
|
||||
|
||||
const fetchCourseBlocksSuccess = (blocks, excludeBlockTypes) => ({
|
||||
type: courseBlocksActions.fetch.SUCCESS,
|
||||
blocks,
|
||||
excludeBlockTypes,
|
||||
type: courseBlocksActions.fetch.SUCCESS,
|
||||
blocks,
|
||||
excludeBlockTypes,
|
||||
});
|
||||
|
||||
const selectBlock = blockId => ({
|
||||
type: courseBlocksActions.SELECT_BLOCK,
|
||||
blockId,
|
||||
type: courseBlocksActions.SELECT_BLOCK,
|
||||
blockId,
|
||||
});
|
||||
|
||||
const changeRoot = blockId => ({
|
||||
type: courseBlocksActions.CHANGE_ROOT,
|
||||
blockId,
|
||||
type: courseBlocksActions.CHANGE_ROOT,
|
||||
blockId,
|
||||
});
|
||||
|
||||
const fetchCourseBlocks = (courseId, excludeBlockTypes) => dispatch =>
|
||||
getCourseBlocks(courseId)
|
||||
.then((response) => {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
throw new Error(response);
|
||||
})
|
||||
.then(
|
||||
json => dispatch(fetchCourseBlocksSuccess(json, excludeBlockTypes)),
|
||||
error => console.log(error), // eslint-disable-line no-console
|
||||
);
|
||||
getCourseBlocks(courseId)
|
||||
.then((response) => {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
throw new Error(response);
|
||||
})
|
||||
.then(
|
||||
json => dispatch(fetchCourseBlocksSuccess(json, excludeBlockTypes)),
|
||||
error => console.log(error), // eslint-disable-line no-console
|
||||
);
|
||||
|
||||
export {
|
||||
fetchCourseBlocks,
|
||||
fetchCourseBlocksSuccess,
|
||||
selectBlock,
|
||||
changeRoot,
|
||||
fetchCourseBlocks,
|
||||
fetchCourseBlocksSuccess,
|
||||
selectBlock,
|
||||
changeRoot,
|
||||
};
|
||||
|
||||
@@ -4,31 +4,31 @@ import 'whatwg-fetch';
|
||||
const COURSE_BLOCKS_API = '/api/courses/v1/blocks/';
|
||||
|
||||
const HEADERS = {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': Cookies.get('csrftoken'),
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': Cookies.get('csrftoken'),
|
||||
};
|
||||
|
||||
export function buildQueryString(data) {
|
||||
return Object.keys(data)
|
||||
.map((key) => {
|
||||
const value = Array.isArray(data[key])
|
||||
? data[key].map(item => encodeURIComponent(item)).join(',')
|
||||
: encodeURIComponent(data[key]);
|
||||
return `${encodeURIComponent(key)}=${value}`;
|
||||
})
|
||||
.join('&');
|
||||
return Object.keys(data)
|
||||
.map((key) => {
|
||||
const value = Array.isArray(data[key])
|
||||
? data[key].map(item => encodeURIComponent(item)).join(',')
|
||||
: encodeURIComponent(data[key]);
|
||||
return `${encodeURIComponent(key)}=${value}`;
|
||||
})
|
||||
.join('&');
|
||||
}
|
||||
|
||||
export const getCourseBlocks = courseId => fetch(
|
||||
`${COURSE_BLOCKS_API}?${buildQueryString({
|
||||
course_id: courseId,
|
||||
all_blocks: true,
|
||||
depth: 'all',
|
||||
requested_fields: ['name', 'display_name', 'block_type', 'children'],
|
||||
})}`, {
|
||||
credentials: 'same-origin',
|
||||
method: 'get',
|
||||
headers: HEADERS,
|
||||
},
|
||||
`${COURSE_BLOCKS_API}?${buildQueryString({
|
||||
course_id: courseId,
|
||||
all_blocks: true,
|
||||
depth: 'all',
|
||||
requested_fields: ['name', 'display_name', 'block_type', 'children'],
|
||||
})}`, {
|
||||
credentials: 'same-origin',
|
||||
method: 'get',
|
||||
headers: HEADERS,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -2,54 +2,54 @@ import { combineReducers } from 'redux';
|
||||
import courseBlocksActions from '../actions/constants';
|
||||
|
||||
export const buildBlockTree = (blocks, excludeBlockTypes) => {
|
||||
if (!(blocks && blocks.root)) return null;
|
||||
const blockTree = (root, parent) => {
|
||||
const tree = Object.assign({ parent }, blocks.blocks[root]);
|
||||
if (tree.children) {
|
||||
tree.children = tree.children.map(block => blockTree(block, root));
|
||||
if (excludeBlockTypes) {
|
||||
tree.children = tree.children.filter(
|
||||
block => !excludeBlockTypes.includes(block.type),
|
||||
);
|
||||
}
|
||||
}
|
||||
return tree;
|
||||
};
|
||||
return blockTree(blocks.root, null);
|
||||
if (!(blocks && blocks.root)) return null;
|
||||
const blockTree = (root, parent) => {
|
||||
const tree = Object.assign({ parent }, blocks.blocks[root]);
|
||||
if (tree.children) {
|
||||
tree.children = tree.children.map(block => blockTree(block, root));
|
||||
if (excludeBlockTypes) {
|
||||
tree.children = tree.children.filter(
|
||||
block => !excludeBlockTypes.includes(block.type),
|
||||
);
|
||||
}
|
||||
}
|
||||
return tree;
|
||||
};
|
||||
return blockTree(blocks.root, null);
|
||||
};
|
||||
|
||||
export const blocks = (state = {}, action) => {
|
||||
switch (action.type) {
|
||||
switch (action.type) {
|
||||
case courseBlocksActions.fetch.SUCCESS:
|
||||
return buildBlockTree(action.blocks, action.excludeBlockTypes);
|
||||
return buildBlockTree(action.blocks, action.excludeBlockTypes);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export const selectedBlock = (state = '', action) => {
|
||||
switch (action.type) {
|
||||
switch (action.type) {
|
||||
case courseBlocksActions.SELECT_BLOCK:
|
||||
return action.blockId;
|
||||
return action.blockId;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export const rootBlock = (state = null, action) => {
|
||||
switch (action.type) {
|
||||
switch (action.type) {
|
||||
case courseBlocksActions.fetch.SUCCESS:
|
||||
return action.blocks.root;
|
||||
return action.blocks.root;
|
||||
case courseBlocksActions.CHANGE_ROOT:
|
||||
return action.blockId;
|
||||
return action.blockId;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default combineReducers({
|
||||
blocks,
|
||||
selectedBlock,
|
||||
rootBlock,
|
||||
blocks,
|
||||
selectedBlock,
|
||||
rootBlock,
|
||||
});
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
export const findBlockWithId = (blockList, blockId) => {
|
||||
if (!blockList) return null;
|
||||
for (let idx = 0; idx < blockList.length; idx += 1) {
|
||||
const block = blockList[idx];
|
||||
if (block.id === blockId) return block;
|
||||
if (!blockList) return null;
|
||||
for (let idx = 0; idx < blockList.length; idx += 1) {
|
||||
const block = blockList[idx];
|
||||
if (block.id === blockId) return block;
|
||||
|
||||
const foundBlock = findBlockWithId(block.children, blockId);
|
||||
if (foundBlock) return foundBlock;
|
||||
}
|
||||
return null;
|
||||
const foundBlock = findBlockWithId(block.children, blockId);
|
||||
if (foundBlock) return foundBlock;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const getActiveBlockTree = (state) => {
|
||||
if (state.rootBlock === state.blocks.id) return state.blocks;
|
||||
return findBlockWithId(state.blocks.children, state.rootBlock);
|
||||
if (state.rootBlock === state.blocks.id) return state.blocks;
|
||||
return findBlockWithId(state.blocks.children, state.rootBlock);
|
||||
};
|
||||
|
||||
@@ -4,9 +4,9 @@ import thunkMiddleware from 'redux-thunk';
|
||||
import rootReducer from './reducers';
|
||||
|
||||
const configureStore = initialState => createStore(
|
||||
rootReducer,
|
||||
initialState,
|
||||
applyMiddleware(thunkMiddleware),
|
||||
rootReducer,
|
||||
initialState,
|
||||
applyMiddleware(thunkMiddleware),
|
||||
);
|
||||
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ import BlockBrowserContainer from './components/BlockBrowser/BlockBrowserContain
|
||||
import store from './data/store';
|
||||
|
||||
export const BlockBrowser = props => (
|
||||
<Provider store={store}>
|
||||
<BlockBrowserContainer {...props} />
|
||||
</Provider>
|
||||
<Provider store={store}>
|
||||
<BlockBrowserContainer {...props} />
|
||||
</Provider>
|
||||
);
|
||||
|
||||
export default BlockBrowser;
|
||||
|
||||
@@ -6,118 +6,118 @@ import PropTypes from 'prop-types';
|
||||
/** Experimental Carousel as part of https://openedx.atlassian.net/browse/LEARNER-3583 **/
|
||||
|
||||
function NextArrow(props) {
|
||||
const {currentSlide, slideCount, onClick, displayedSlides} = props;
|
||||
const showArrow = slideCount - currentSlide > displayedSlides;
|
||||
const opts = {
|
||||
className: classNames('js-carousel-nav', 'carousel-arrow', 'next', 'btn btn-secondary', {'active': showArrow}),
|
||||
onClick
|
||||
};
|
||||
const {currentSlide, slideCount, onClick, displayedSlides} = props;
|
||||
const showArrow = slideCount - currentSlide > displayedSlides;
|
||||
const opts = {
|
||||
className: classNames('js-carousel-nav', 'carousel-arrow', 'next', 'btn btn-secondary', {'active': showArrow}),
|
||||
onClick
|
||||
};
|
||||
|
||||
if (!showArrow) {
|
||||
opts.disabled = 'disabled';
|
||||
}
|
||||
if (!showArrow) {
|
||||
opts.disabled = 'disabled';
|
||||
}
|
||||
|
||||
return (
|
||||
<button {...opts}>
|
||||
<span>Next </span>
|
||||
<span className="icon fa fa-chevron-right" aria-hidden="true"></span>
|
||||
<span className="sr">{ 'Scroll carousel forwards' }</span>
|
||||
</button>
|
||||
);
|
||||
return (
|
||||
<button {...opts}>
|
||||
<span>Next </span>
|
||||
<span className="icon fa fa-chevron-right" aria-hidden="true"></span>
|
||||
<span className="sr">{ 'Scroll carousel forwards' }</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
function PrevArrow(props) {
|
||||
const {currentSlide, onClick} = props;
|
||||
const showArrow = currentSlide > 0;
|
||||
const opts = {
|
||||
className: classNames('js-carousel-nav', 'carousel-arrow', 'prev', 'btn btn-secondary', {'active': showArrow}),
|
||||
onClick
|
||||
};
|
||||
const {currentSlide, onClick} = props;
|
||||
const showArrow = currentSlide > 0;
|
||||
const opts = {
|
||||
className: classNames('js-carousel-nav', 'carousel-arrow', 'prev', 'btn btn-secondary', {'active': showArrow}),
|
||||
onClick
|
||||
};
|
||||
|
||||
if (!showArrow) {
|
||||
opts.disabled = 'disabled';
|
||||
}
|
||||
if (!showArrow) {
|
||||
opts.disabled = 'disabled';
|
||||
}
|
||||
|
||||
return (
|
||||
<button {...opts} >
|
||||
<span className="icon fa fa-chevron-left" aria-hidden="true"></span>
|
||||
<span> Prev</span>
|
||||
<span className="sr">{ 'Scroll carousel backwards' }</span>
|
||||
<span className="icon fa fa-chevron-left" aria-hidden="true"></span>
|
||||
<span> Prev</span>
|
||||
<span className="sr">{ 'Scroll carousel backwards' }</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export default class ExperimentalCarousel extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
// Default to undefined to not focus on page load
|
||||
activeIndex: undefined,
|
||||
};
|
||||
this.state = {
|
||||
// Default to undefined to not focus on page load
|
||||
activeIndex: undefined,
|
||||
};
|
||||
|
||||
this.carousels = [];
|
||||
this.carousels = [];
|
||||
|
||||
this.afterChange = this.afterChange.bind(this);
|
||||
this.getCarouselContent = this.getCarouselContent.bind(this);
|
||||
}
|
||||
|
||||
afterChange(activeIndex) {
|
||||
this.setState({ activeIndex });
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const { activeIndex } = this.state;
|
||||
|
||||
if (!isNaN(activeIndex)) {
|
||||
this.carousels[activeIndex].focus();
|
||||
this.afterChange = this.afterChange.bind(this);
|
||||
this.getCarouselContent = this.getCarouselContent.bind(this);
|
||||
}
|
||||
}
|
||||
|
||||
getCarouselContent() {
|
||||
return this.props.slides.map((slide, i) => {
|
||||
const firstIndex = this.state.activeIndex || 0;
|
||||
const lastIndex = firstIndex + this.props.slides.length;
|
||||
const tabIndex = (firstIndex <= i && i < lastIndex) ? undefined : '-1';
|
||||
const carouselLinkProps = {
|
||||
ref: (item) => {
|
||||
this.carousels[i] = item;
|
||||
},
|
||||
tabIndex: tabIndex,
|
||||
className: 'carousel-item'
|
||||
}
|
||||
afterChange(activeIndex) {
|
||||
this.setState({ activeIndex });
|
||||
}
|
||||
|
||||
return (
|
||||
<div {...carouselLinkProps}>
|
||||
{ slide }
|
||||
</div>
|
||||
componentDidUpdate() {
|
||||
const { activeIndex } = this.state;
|
||||
|
||||
if (!isNaN(activeIndex)) {
|
||||
this.carousels[activeIndex].focus();
|
||||
}
|
||||
}
|
||||
|
||||
getCarouselContent() {
|
||||
return this.props.slides.map((slide, i) => {
|
||||
const firstIndex = this.state.activeIndex || 0;
|
||||
const lastIndex = firstIndex + this.props.slides.length;
|
||||
const tabIndex = (firstIndex <= i && i < lastIndex) ? undefined : '-1';
|
||||
const carouselLinkProps = {
|
||||
ref: (item) => {
|
||||
this.carousels[i] = item;
|
||||
},
|
||||
tabIndex: tabIndex,
|
||||
className: 'carousel-item'
|
||||
}
|
||||
|
||||
return (
|
||||
<div {...carouselLinkProps}>
|
||||
{ slide }
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const carouselSettings = {
|
||||
accessibility: true,
|
||||
dots: true,
|
||||
infinite: false,
|
||||
speed: 500,
|
||||
className: 'carousel-wrapper',
|
||||
nextArrow: <NextArrow displayedSlides={1} />,
|
||||
prevArrow: <PrevArrow />,
|
||||
afterChange: this.afterChange,
|
||||
slidesToShow: 1,
|
||||
slidesToScroll: 1,
|
||||
initialSlide: 0,
|
||||
};
|
||||
|
||||
return (
|
||||
<Slider {...carouselSettings} >
|
||||
{this.getCarouselContent()}
|
||||
</Slider>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const carouselSettings = {
|
||||
accessibility: true,
|
||||
dots: true,
|
||||
infinite: false,
|
||||
speed: 500,
|
||||
className: 'carousel-wrapper',
|
||||
nextArrow: <NextArrow displayedSlides={1} />,
|
||||
prevArrow: <PrevArrow />,
|
||||
afterChange: this.afterChange,
|
||||
slidesToShow: 1,
|
||||
slidesToScroll: 1,
|
||||
initialSlide: 0,
|
||||
};
|
||||
|
||||
return (
|
||||
<Slider {...carouselSettings} >
|
||||
{this.getCarouselContent()}
|
||||
</Slider>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ExperimentalCarousel.propTypes = {
|
||||
slides: PropTypes.array.isRequired
|
||||
slides: PropTypes.array.isRequired
|
||||
};
|
||||
@@ -7,7 +7,7 @@
|
||||
/* RequireJS */
|
||||
define(['jquery', 'underscore', 'gettext', 'common/js/components/views/feedback_notification',
|
||||
'common/js/components/views/feedback_prompt', 'edx-ui-toolkit/js/utils/html-utils'],
|
||||
function($, _, gettext, NotificationView, PromptView, HtmlUtils) {
|
||||
function($, _, gettext, NotificationView, PromptView, HtmlUtils) {
|
||||
/* End RequireJS */
|
||||
/* Webpack
|
||||
define(['jquery', 'underscore', 'gettext', 'common/js/components/views/feedback_notification',
|
||||
@@ -15,102 +15,102 @@
|
||||
function($, _, gettext, NotificationView, PromptView, $script) {
|
||||
/* End Webpack */
|
||||
|
||||
var toggleExpandCollapse, showLoadingIndicator, hideLoadingIndicator, confirmThenRunOperation,
|
||||
runOperationShowingMessage, showErrorMeassage, withDisabledElement, disableElementWhileRunning,
|
||||
getScrollOffset, setScrollOffset, setScrollTop, redirect, reload, hasChangedAttributes,
|
||||
deleteNotificationHandler, validateRequiredField, validateURLItemEncoding,
|
||||
validateTotalKeyLength, checkTotalKeyLengthViolations, loadJavaScript;
|
||||
var toggleExpandCollapse, showLoadingIndicator, hideLoadingIndicator, confirmThenRunOperation,
|
||||
runOperationShowingMessage, showErrorMeassage, withDisabledElement, disableElementWhileRunning,
|
||||
getScrollOffset, setScrollOffset, setScrollTop, redirect, reload, hasChangedAttributes,
|
||||
deleteNotificationHandler, validateRequiredField, validateURLItemEncoding,
|
||||
validateTotalKeyLength, checkTotalKeyLengthViolations, loadJavaScript;
|
||||
|
||||
// see https://openedx.atlassian.net/browse/TNL-889 for what is it and why it's 65
|
||||
var MAX_SUM_KEY_LENGTH = 65;
|
||||
// see https://openedx.atlassian.net/browse/TNL-889 for what is it and why it's 65
|
||||
var MAX_SUM_KEY_LENGTH = 65;
|
||||
|
||||
/**
|
||||
/**
|
||||
* Toggles the expanded state of the current element.
|
||||
*/
|
||||
toggleExpandCollapse = function(target, collapsedClass) {
|
||||
// Support the old 'collapsed' option until fully switched over to is-collapsed
|
||||
var collapsed = collapsedClass || 'collapsed';
|
||||
target.closest('.expand-collapse').toggleClass('expand collapse');
|
||||
target.closest('.is-collapsible, .window').toggleClass(collapsed);
|
||||
target.closest('.is-collapsible').children('article').slideToggle();
|
||||
};
|
||||
toggleExpandCollapse = function(target, collapsedClass) {
|
||||
// Support the old 'collapsed' option until fully switched over to is-collapsed
|
||||
var collapsed = collapsedClass || 'collapsed';
|
||||
target.closest('.expand-collapse').toggleClass('expand collapse');
|
||||
target.closest('.is-collapsible, .window').toggleClass(collapsed);
|
||||
target.closest('.is-collapsible').children('article').slideToggle();
|
||||
};
|
||||
|
||||
/**
|
||||
/**
|
||||
* Show the page's loading indicator.
|
||||
*/
|
||||
showLoadingIndicator = function() {
|
||||
$('.ui-loading').show();
|
||||
};
|
||||
showLoadingIndicator = function() {
|
||||
$('.ui-loading').show();
|
||||
};
|
||||
|
||||
/**
|
||||
/**
|
||||
* Hide the page's loading indicator.
|
||||
*/
|
||||
hideLoadingIndicator = function() {
|
||||
$('.ui-loading').hide();
|
||||
};
|
||||
hideLoadingIndicator = function() {
|
||||
$('.ui-loading').hide();
|
||||
};
|
||||
|
||||
/**
|
||||
/**
|
||||
* Confirms with the user whether to run an operation or not, and then runs it if desired.
|
||||
*/
|
||||
confirmThenRunOperation = function(title, message, actionLabel, operation, onCancelCallback) {
|
||||
return new PromptView.Warning({
|
||||
title: title,
|
||||
message: message,
|
||||
actions: {
|
||||
primary: {
|
||||
text: actionLabel,
|
||||
click: function(prompt) {
|
||||
prompt.hide();
|
||||
operation();
|
||||
}
|
||||
},
|
||||
secondary: {
|
||||
text: gettext('Cancel'),
|
||||
click: function(prompt) {
|
||||
if (onCancelCallback) {
|
||||
onCancelCallback();
|
||||
}
|
||||
return prompt.hide();
|
||||
confirmThenRunOperation = function(title, message, actionLabel, operation, onCancelCallback) {
|
||||
return new PromptView.Warning({
|
||||
title: title,
|
||||
message: message,
|
||||
actions: {
|
||||
primary: {
|
||||
text: actionLabel,
|
||||
click: function(prompt) {
|
||||
prompt.hide();
|
||||
operation();
|
||||
}
|
||||
},
|
||||
secondary: {
|
||||
text: gettext('Cancel'),
|
||||
click: function(prompt) {
|
||||
if (onCancelCallback) {
|
||||
onCancelCallback();
|
||||
}
|
||||
return prompt.hide();
|
||||
}
|
||||
}
|
||||
}).show();
|
||||
};
|
||||
}
|
||||
}).show();
|
||||
};
|
||||
|
||||
/**
|
||||
/**
|
||||
* Shows a progress message for the duration of an asynchronous operation.
|
||||
* Note: this does not remove the notification upon failure because an error
|
||||
* will be shown that shouldn't be removed.
|
||||
* @param message The message to show.
|
||||
* @param operation A function that returns a promise representing the operation.
|
||||
*/
|
||||
runOperationShowingMessage = function(message, operation) {
|
||||
var notificationView;
|
||||
notificationView = new NotificationView.Mini({
|
||||
title: gettext(message)
|
||||
});
|
||||
notificationView.show();
|
||||
return operation().done(function() {
|
||||
notificationView.hide();
|
||||
});
|
||||
};
|
||||
runOperationShowingMessage = function(message, operation) {
|
||||
var notificationView;
|
||||
notificationView = new NotificationView.Mini({
|
||||
title: gettext(message)
|
||||
});
|
||||
notificationView.show();
|
||||
return operation().done(function() {
|
||||
notificationView.hide();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
/**
|
||||
* Shows an error notification message for a specifc period of time.
|
||||
* @param heading The heading of notification.
|
||||
* @param message The message to show.
|
||||
* @param timeInterval The time interval to hide the notification.
|
||||
*/
|
||||
showErrorMeassage = function(heading, message, timeInterval) {
|
||||
var errorNotificationView = new NotificationView.Error({
|
||||
title: gettext(heading),
|
||||
message: gettext(message)
|
||||
});
|
||||
errorNotificationView.show();
|
||||
showErrorMeassage = function(heading, message, timeInterval) {
|
||||
var errorNotificationView = new NotificationView.Error({
|
||||
title: gettext(heading),
|
||||
message: gettext(message)
|
||||
});
|
||||
errorNotificationView.show();
|
||||
|
||||
setTimeout(function() { errorNotificationView.hide(); }, timeInterval);
|
||||
};
|
||||
/**
|
||||
setTimeout(function() { errorNotificationView.hide(); }, timeInterval);
|
||||
};
|
||||
/**
|
||||
* Wraps a Backbone event callback to disable the event's target element.
|
||||
*
|
||||
* This paradigm is designed to be used in Backbone event maps where
|
||||
@@ -119,212 +119,212 @@
|
||||
* @param functionName the function to execute, as a string.
|
||||
* The function must return a jQuery promise and be able to take an event
|
||||
*/
|
||||
withDisabledElement = function(functionName) {
|
||||
return function(event) {
|
||||
var view = this;
|
||||
disableElementWhileRunning($(event.currentTarget), function() {
|
||||
// call view.functionName(event), with view as the current this
|
||||
return view[functionName].apply(view, [event]);
|
||||
});
|
||||
};
|
||||
withDisabledElement = function(functionName) {
|
||||
return function(event) {
|
||||
var view = this;
|
||||
disableElementWhileRunning($(event.currentTarget), function() {
|
||||
// call view.functionName(event), with view as the current this
|
||||
return view[functionName].apply(view, [event]);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
/**
|
||||
* Disables a given element when a given operation is running.
|
||||
* @param {jQuery} element the element to be disabled.
|
||||
* @param operation the operation during whose duration the
|
||||
* element should be disabled. The operation should return
|
||||
* a JQuery promise.
|
||||
*/
|
||||
disableElementWhileRunning = function(element, operation) {
|
||||
element.addClass('is-disabled').attr('aria-disabled', true);
|
||||
return operation().always(function() {
|
||||
element.removeClass('is-disabled').attr('aria-disabled', false);
|
||||
});
|
||||
};
|
||||
disableElementWhileRunning = function(element, operation) {
|
||||
element.addClass('is-disabled').attr('aria-disabled', true);
|
||||
return operation().always(function() {
|
||||
element.removeClass('is-disabled').attr('aria-disabled', false);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
/**
|
||||
* Returns a handler that removes a notification, both dismissing it and deleting it from the database.
|
||||
* @param callback function to call when deletion succeeds
|
||||
*/
|
||||
deleteNotificationHandler = function(callback) {
|
||||
return function(event) {
|
||||
event.preventDefault();
|
||||
$.ajax({
|
||||
url: $(this).data('dismiss-link'),
|
||||
type: 'DELETE',
|
||||
success: callback
|
||||
});
|
||||
};
|
||||
deleteNotificationHandler = function(callback) {
|
||||
return function(event) {
|
||||
event.preventDefault();
|
||||
$.ajax({
|
||||
url: $(this).data('dismiss-link'),
|
||||
type: 'DELETE',
|
||||
success: callback
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
/**
|
||||
* Performs an animated scroll so that the window has the specified scroll top.
|
||||
* @param scrollTop The desired scroll top for the window.
|
||||
*/
|
||||
setScrollTop = function(scrollTop) {
|
||||
$('html, body').animate({
|
||||
scrollTop: scrollTop
|
||||
}, 500);
|
||||
};
|
||||
setScrollTop = function(scrollTop) {
|
||||
$('html, body').animate({
|
||||
scrollTop: scrollTop
|
||||
}, 500);
|
||||
};
|
||||
|
||||
/**
|
||||
/**
|
||||
* Returns the relative position that the element is scrolled from the top of the view port.
|
||||
* @param element The element in question.
|
||||
*/
|
||||
getScrollOffset = function(element) {
|
||||
var elementTop = element.offset().top;
|
||||
return elementTop - $(window).scrollTop();
|
||||
};
|
||||
getScrollOffset = function(element) {
|
||||
var elementTop = element.offset().top;
|
||||
return elementTop - $(window).scrollTop();
|
||||
};
|
||||
|
||||
/**
|
||||
/**
|
||||
* Scrolls the window so that the element is scrolled down to the specified relative position
|
||||
* from the top of the view port.
|
||||
* @param element The element in question.
|
||||
* @param offset The amount by which the element should be scrolled from the top of the view port.
|
||||
*/
|
||||
setScrollOffset = function(element, offset) {
|
||||
var elementTop = element.offset().top,
|
||||
newScrollTop = elementTop - offset;
|
||||
setScrollTop(newScrollTop);
|
||||
};
|
||||
setScrollOffset = function(element, offset) {
|
||||
var elementTop = element.offset().top,
|
||||
newScrollTop = elementTop - offset;
|
||||
setScrollTop(newScrollTop);
|
||||
};
|
||||
|
||||
/**
|
||||
/**
|
||||
* Redirects to the specified URL. This is broken out as its own function for unit testing.
|
||||
*/
|
||||
redirect = function(url) {
|
||||
window.location = url;
|
||||
};
|
||||
redirect = function(url) {
|
||||
window.location = url;
|
||||
};
|
||||
|
||||
/**
|
||||
/**
|
||||
* Reloads the page. This is broken out as its own function for unit testing.
|
||||
*/
|
||||
reload = function() {
|
||||
window.location.reload();
|
||||
};
|
||||
reload = function() {
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
/**
|
||||
/**
|
||||
* Returns true if a model has changes to at least one of the specified attributes.
|
||||
* @param model The model in question.
|
||||
* @param attributes The list of attributes to be compared.
|
||||
* @returns {boolean} Returns true if attribute changes are found.
|
||||
*/
|
||||
hasChangedAttributes = function(model, attributes) {
|
||||
var i,
|
||||
changedAttributes = model.changedAttributes();
|
||||
if (!changedAttributes) {
|
||||
return false;
|
||||
}
|
||||
for (i = 0; i < attributes.length; i++) {
|
||||
if (_.has(changedAttributes, attributes[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
hasChangedAttributes = function(model, attributes) {
|
||||
var i,
|
||||
changedAttributes = model.changedAttributes();
|
||||
if (!changedAttributes) {
|
||||
return false;
|
||||
};
|
||||
}
|
||||
for (i = 0; i < attributes.length; i++) {
|
||||
if (_.has(changedAttributes, attributes[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
/**
|
||||
* Helper method for course/library creation - verifies a required field is not blank.
|
||||
*/
|
||||
validateRequiredField = function(msg) {
|
||||
return msg.length === 0 ? gettext('Required field.') : '';
|
||||
};
|
||||
validateRequiredField = function(msg) {
|
||||
return msg.length === 0 ? gettext('Required field.') : '';
|
||||
};
|
||||
|
||||
/**
|
||||
/**
|
||||
* Helper method for course/library creation.
|
||||
* Check that a course (org, number, run) doesn't use any special characters
|
||||
*/
|
||||
validateURLItemEncoding = function(item, allowUnicode) {
|
||||
var required = validateRequiredField(item);
|
||||
if (required) {
|
||||
return required;
|
||||
validateURLItemEncoding = function(item, allowUnicode) {
|
||||
var required = validateRequiredField(item);
|
||||
if (required) {
|
||||
return required;
|
||||
}
|
||||
if (allowUnicode) {
|
||||
if (/\s/g.test(item)) {
|
||||
return gettext('Please do not use any spaces in this field.');
|
||||
}
|
||||
if (allowUnicode) {
|
||||
if (/\s/g.test(item)) {
|
||||
return gettext('Please do not use any spaces in this field.');
|
||||
}
|
||||
} else {
|
||||
if (item !== encodeURIComponent(item) || item.match(/[!'()*]/)) {
|
||||
return gettext('Please do not use any spaces or special characters in this field.');
|
||||
}
|
||||
} else {
|
||||
if (item !== encodeURIComponent(item) || item.match(/[!'()*]/)) {
|
||||
return gettext('Please do not use any spaces or special characters in this field.');
|
||||
}
|
||||
return '';
|
||||
};
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
// Ensure that sum length of key field values <= ${MAX_SUM_KEY_LENGTH} chars.
|
||||
validateTotalKeyLength = function(keyFieldSelectors) {
|
||||
var totalLength = _.reduce(
|
||||
keyFieldSelectors,
|
||||
function(sum, ele) { return sum + $(ele).val().length; },
|
||||
0
|
||||
// Ensure that sum length of key field values <= ${MAX_SUM_KEY_LENGTH} chars.
|
||||
validateTotalKeyLength = function(keyFieldSelectors) {
|
||||
var totalLength = _.reduce(
|
||||
keyFieldSelectors,
|
||||
function(sum, ele) { return sum + $(ele).val().length; },
|
||||
0
|
||||
);
|
||||
return totalLength <= MAX_SUM_KEY_LENGTH;
|
||||
};
|
||||
|
||||
checkTotalKeyLengthViolations = function(selectors, classes, keyFieldSelectors, messageTpl) {
|
||||
var tempHtml;
|
||||
if (!validateTotalKeyLength(keyFieldSelectors)) {
|
||||
$(selectors.errorWrapper).addClass(classes.shown).removeClass(classes.hiding);
|
||||
tempHtml = HtmlUtils.joinHtml(
|
||||
HtmlUtils.HTML('<p>'),
|
||||
HtmlUtils.template(messageTpl)({limit: MAX_SUM_KEY_LENGTH}),
|
||||
HtmlUtils.HTML('</p>')
|
||||
);
|
||||
return totalLength <= MAX_SUM_KEY_LENGTH;
|
||||
};
|
||||
HtmlUtils.setHtml(
|
||||
$(selectors.errorMessage),
|
||||
tempHtml
|
||||
);
|
||||
$(selectors.save).addClass(classes.disabled);
|
||||
} else {
|
||||
$(selectors.errorWrapper).removeClass(classes.shown).addClass(classes.hiding);
|
||||
}
|
||||
};
|
||||
|
||||
checkTotalKeyLengthViolations = function(selectors, classes, keyFieldSelectors, messageTpl) {
|
||||
var tempHtml;
|
||||
if (!validateTotalKeyLength(keyFieldSelectors)) {
|
||||
$(selectors.errorWrapper).addClass(classes.shown).removeClass(classes.hiding);
|
||||
tempHtml = HtmlUtils.joinHtml(
|
||||
HtmlUtils.HTML('<p>'),
|
||||
HtmlUtils.template(messageTpl)({limit: MAX_SUM_KEY_LENGTH}),
|
||||
HtmlUtils.HTML('</p>')
|
||||
);
|
||||
HtmlUtils.setHtml(
|
||||
$(selectors.errorMessage),
|
||||
tempHtml
|
||||
);
|
||||
$(selectors.save).addClass(classes.disabled);
|
||||
} else {
|
||||
$(selectors.errorWrapper).removeClass(classes.shown).addClass(classes.hiding);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
/**
|
||||
* Dynamically loads the specified JavaScript file.
|
||||
* @param url The URL to a JavaScript file.
|
||||
* @returns {Promise} A promise indicating when the URL has been loaded.
|
||||
*/
|
||||
loadJavaScript = function(url) {
|
||||
var deferred = $.Deferred();
|
||||
/* RequireJS */
|
||||
require([url],
|
||||
function() {
|
||||
deferred.resolve();
|
||||
},
|
||||
function() {
|
||||
deferred.reject();
|
||||
});
|
||||
/* End RequireJS */
|
||||
/* Webpack
|
||||
loadJavaScript = function(url) {
|
||||
var deferred = $.Deferred();
|
||||
/* RequireJS */
|
||||
require([url],
|
||||
function() {
|
||||
deferred.resolve();
|
||||
},
|
||||
function() {
|
||||
deferred.reject();
|
||||
});
|
||||
/* End RequireJS */
|
||||
/* Webpack
|
||||
$script(url, url, function () {
|
||||
deferred.resolve();
|
||||
});
|
||||
/* End Webpack */
|
||||
return deferred.promise();
|
||||
};
|
||||
return deferred.promise();
|
||||
};
|
||||
|
||||
return {
|
||||
toggleExpandCollapse: toggleExpandCollapse,
|
||||
showLoadingIndicator: showLoadingIndicator,
|
||||
hideLoadingIndicator: hideLoadingIndicator,
|
||||
confirmThenRunOperation: confirmThenRunOperation,
|
||||
runOperationShowingMessage: runOperationShowingMessage,
|
||||
showErrorMeassage: showErrorMeassage,
|
||||
withDisabledElement: withDisabledElement,
|
||||
disableElementWhileRunning: disableElementWhileRunning,
|
||||
deleteNotificationHandler: deleteNotificationHandler,
|
||||
setScrollTop: setScrollTop,
|
||||
getScrollOffset: getScrollOffset,
|
||||
setScrollOffset: setScrollOffset,
|
||||
redirect: redirect,
|
||||
reload: reload,
|
||||
hasChangedAttributes: hasChangedAttributes,
|
||||
validateRequiredField: validateRequiredField,
|
||||
validateURLItemEncoding: validateURLItemEncoding,
|
||||
validateTotalKeyLength: validateTotalKeyLength,
|
||||
checkTotalKeyLengthViolations: checkTotalKeyLengthViolations,
|
||||
loadJavaScript: loadJavaScript
|
||||
};
|
||||
});
|
||||
return {
|
||||
toggleExpandCollapse: toggleExpandCollapse,
|
||||
showLoadingIndicator: showLoadingIndicator,
|
||||
hideLoadingIndicator: hideLoadingIndicator,
|
||||
confirmThenRunOperation: confirmThenRunOperation,
|
||||
runOperationShowingMessage: runOperationShowingMessage,
|
||||
showErrorMeassage: showErrorMeassage,
|
||||
withDisabledElement: withDisabledElement,
|
||||
disableElementWhileRunning: disableElementWhileRunning,
|
||||
deleteNotificationHandler: deleteNotificationHandler,
|
||||
setScrollTop: setScrollTop,
|
||||
getScrollOffset: getScrollOffset,
|
||||
setScrollOffset: setScrollOffset,
|
||||
redirect: redirect,
|
||||
reload: reload,
|
||||
hasChangedAttributes: hasChangedAttributes,
|
||||
validateRequiredField: validateRequiredField,
|
||||
validateURLItemEncoding: validateURLItemEncoding,
|
||||
validateTotalKeyLength: validateTotalKeyLength,
|
||||
checkTotalKeyLengthViolations: checkTotalKeyLengthViolations,
|
||||
loadJavaScript: loadJavaScript
|
||||
};
|
||||
});
|
||||
}).call(this, define || RequireJS.define, require || RequireJS.require);
|
||||
|
||||
@@ -8,30 +8,30 @@
|
||||
'edx-ui-toolkit/js/utils/html-utils',
|
||||
'text!../../../../common/templates/components/system-feedback.underscore'
|
||||
],
|
||||
function($, _, str, Backbone, HtmlUtils, systemFeedbackTemplate) {
|
||||
var tabbableElements = [
|
||||
"a[href]:not([tabindex='-1'])",
|
||||
"area[href]:not([tabindex='-1'])",
|
||||
"input:not([disabled]):not([tabindex='-1'])",
|
||||
"select:not([disabled]):not([tabindex='-1'])",
|
||||
"textarea:not([disabled]):not([tabindex='-1'])",
|
||||
"button:not([disabled]):not([tabindex='-1'])",
|
||||
"iframe:not([tabindex='-1'])",
|
||||
"[tabindex]:not([tabindex='-1'])",
|
||||
"[contentEditable=true]:not([tabindex='-1'])"
|
||||
];
|
||||
var SystemFeedback = Backbone.View.extend({
|
||||
options: {
|
||||
title: '',
|
||||
message: '',
|
||||
intent: null, // "warning", "confirmation", "error", "announcement", "step-required", etc
|
||||
type: null, // "alert", "notification", or "prompt": set by subclass
|
||||
shown: true, // is this view currently being shown?
|
||||
icon: true, // should we render an icon related to the message intent?
|
||||
closeIcon: true, // should we render a close button in the top right corner?
|
||||
minShown: 0, // ms after this view has been shown before it can be hidden
|
||||
maxShown: Infinity, // ms after this view has been shown before it will be automatically hidden
|
||||
outFocusElement: null // element to send focus to on hide
|
||||
function($, _, str, Backbone, HtmlUtils, systemFeedbackTemplate) {
|
||||
var tabbableElements = [
|
||||
"a[href]:not([tabindex='-1'])",
|
||||
"area[href]:not([tabindex='-1'])",
|
||||
"input:not([disabled]):not([tabindex='-1'])",
|
||||
"select:not([disabled]):not([tabindex='-1'])",
|
||||
"textarea:not([disabled]):not([tabindex='-1'])",
|
||||
"button:not([disabled]):not([tabindex='-1'])",
|
||||
"iframe:not([tabindex='-1'])",
|
||||
"[tabindex]:not([tabindex='-1'])",
|
||||
"[contentEditable=true]:not([tabindex='-1'])"
|
||||
];
|
||||
var SystemFeedback = Backbone.View.extend({
|
||||
options: {
|
||||
title: '',
|
||||
message: '',
|
||||
intent: null, // "warning", "confirmation", "error", "announcement", "step-required", etc
|
||||
type: null, // "alert", "notification", or "prompt": set by subclass
|
||||
shown: true, // is this view currently being shown?
|
||||
icon: true, // should we render an icon related to the message intent?
|
||||
closeIcon: true, // should we render a close button in the top right corner?
|
||||
minShown: 0, // ms after this view has been shown before it can be hidden
|
||||
maxShown: Infinity, // ms after this view has been shown before it will be automatically hidden
|
||||
outFocusElement: null // element to send focus to on hide
|
||||
|
||||
/* Could also have an "actions" hash: here is an example demonstrating
|
||||
the expected structure. For each action, by default the framework
|
||||
@@ -60,143 +60,143 @@
|
||||
]
|
||||
}
|
||||
*/
|
||||
},
|
||||
},
|
||||
|
||||
initialize: function(options) {
|
||||
this.options = _.extend({}, this.options, options);
|
||||
if (!this.options.type) {
|
||||
throw 'SystemFeedback: type required (given ' + // eslint-disable-line no-throw-literal
|
||||
initialize: function(options) {
|
||||
this.options = _.extend({}, this.options, options);
|
||||
if (!this.options.type) {
|
||||
throw 'SystemFeedback: type required (given ' + // eslint-disable-line no-throw-literal
|
||||
JSON.stringify(this.options) + ')';
|
||||
}
|
||||
if (!this.options.intent) {
|
||||
throw 'SystemFeedback: intent required (given ' + // eslint-disable-line no-throw-literal
|
||||
JSON.stringify(this.options) + ')';
|
||||
}
|
||||
this.setElement($('#page-' + this.options.type));
|
||||
// handle single "secondary" action
|
||||
if (this.options.actions && this.options.actions.secondary &&
|
||||
!_.isArray(this.options.actions.secondary)) {
|
||||
this.options.actions.secondary = [this.options.actions.secondary];
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
inFocus: function(wrapperElementSelector) {
|
||||
var wrapper = wrapperElementSelector || '.wrapper',
|
||||
tabbables;
|
||||
this.options.outFocusElement = this.options.outFocusElement || document.activeElement;
|
||||
|
||||
// Set focus to the container.
|
||||
this.$(wrapper).first().focus();
|
||||
|
||||
// Make tabs within the prompt loop rather than setting focus
|
||||
// back to the main content of the page.
|
||||
tabbables = this.$(tabbableElements.join());
|
||||
tabbables.on('keydown', function(event) {
|
||||
// On tab backward from the first tabbable item in the prompt
|
||||
if (event.which === 9 && event.shiftKey && event.target === tabbables.first()[0]) {
|
||||
event.preventDefault();
|
||||
tabbables.last().focus();
|
||||
} else if (event.which === 9 && !event.shiftKey && event.target === tabbables.last()[0]) {
|
||||
// On tab forward from the last tabbable item in the prompt
|
||||
event.preventDefault();
|
||||
tabbables.first().focus();
|
||||
}
|
||||
});
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
outFocus: function() {
|
||||
this.$(tabbableElements.join()).off('keydown');
|
||||
if (this.options.outFocusElement) {
|
||||
this.options.outFocusElement.focus();
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
// public API: show() and hide()
|
||||
show: function() {
|
||||
clearTimeout(this.hideTimeout);
|
||||
this.options.shown = true;
|
||||
this.shownAt = new Date();
|
||||
this.render();
|
||||
if ($.isNumeric(this.options.maxShown)) {
|
||||
this.hideTimeout = setTimeout(_.bind(this.hide, this),
|
||||
this.options.maxShown);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
hide: function() {
|
||||
if (this.shownAt && $.isNumeric(this.options.minShown) &&
|
||||
this.options.minShown > new Date() - this.shownAt) {
|
||||
clearTimeout(this.hideTimeout);
|
||||
this.hideTimeout = setTimeout(_.bind(this.hide, this),
|
||||
this.options.minShown - (new Date() - this.shownAt));
|
||||
} else {
|
||||
this.options.shown = false;
|
||||
delete this.shownAt;
|
||||
this.render();
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
// the rest of the API should be considered semi-private
|
||||
events: {
|
||||
'click .action-close': 'hide',
|
||||
'click .action-primary': 'primaryClick',
|
||||
'click .action-secondary': 'secondaryClick'
|
||||
},
|
||||
|
||||
render: function() {
|
||||
// there can be only one active view of a given type at a time: only
|
||||
// one alert, only one notification, only one prompt. Therefore, we'll
|
||||
// use a singleton approach.
|
||||
var singleton = SystemFeedback['active_' + this.options.type];
|
||||
if (singleton && singleton !== this) {
|
||||
singleton.stopListening();
|
||||
singleton.undelegateEvents();
|
||||
}
|
||||
HtmlUtils.setHtml(this.$el, HtmlUtils.template(systemFeedbackTemplate)(this.options));
|
||||
SystemFeedback['active_' + this.options.type] = this;
|
||||
return this;
|
||||
},
|
||||
|
||||
primaryClick: function(event) {
|
||||
var actions, primary;
|
||||
actions = this.options.actions;
|
||||
if (!actions) { return; }
|
||||
primary = actions.primary;
|
||||
if (!primary) { return; }
|
||||
if (primary.preventDefault !== false) {
|
||||
event.preventDefault();
|
||||
}
|
||||
if (primary.click) {
|
||||
primary.click.call(event.target, this, event);
|
||||
}
|
||||
},
|
||||
|
||||
secondaryClick: function(event) {
|
||||
var actions, secondaryList, secondary, i;
|
||||
actions = this.options.actions;
|
||||
if (!actions) { return; }
|
||||
secondaryList = actions.secondary;
|
||||
if (!secondaryList) { return; }
|
||||
// which secondary action was clicked?
|
||||
i = 0; // default to the first secondary action (easier for testing)
|
||||
if (event && event.target) {
|
||||
i = _.indexOf(this.$('.action-secondary'), event.target);
|
||||
}
|
||||
secondary = secondaryList[i];
|
||||
if (secondary.preventDefault !== false) {
|
||||
event.preventDefault();
|
||||
}
|
||||
if (secondary.click) {
|
||||
secondary.click.call(event.target, this, event);
|
||||
}
|
||||
}
|
||||
});
|
||||
return SystemFeedback;
|
||||
if (!this.options.intent) {
|
||||
throw 'SystemFeedback: intent required (given ' + // eslint-disable-line no-throw-literal
|
||||
JSON.stringify(this.options) + ')';
|
||||
}
|
||||
this.setElement($('#page-' + this.options.type));
|
||||
// handle single "secondary" action
|
||||
if (this.options.actions && this.options.actions.secondary &&
|
||||
!_.isArray(this.options.actions.secondary)) {
|
||||
this.options.actions.secondary = [this.options.actions.secondary];
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
inFocus: function(wrapperElementSelector) {
|
||||
var wrapper = wrapperElementSelector || '.wrapper',
|
||||
tabbables;
|
||||
this.options.outFocusElement = this.options.outFocusElement || document.activeElement;
|
||||
|
||||
// Set focus to the container.
|
||||
this.$(wrapper).first().focus();
|
||||
|
||||
// Make tabs within the prompt loop rather than setting focus
|
||||
// back to the main content of the page.
|
||||
tabbables = this.$(tabbableElements.join());
|
||||
tabbables.on('keydown', function(event) {
|
||||
// On tab backward from the first tabbable item in the prompt
|
||||
if (event.which === 9 && event.shiftKey && event.target === tabbables.first()[0]) {
|
||||
event.preventDefault();
|
||||
tabbables.last().focus();
|
||||
} else if (event.which === 9 && !event.shiftKey && event.target === tabbables.last()[0]) {
|
||||
// On tab forward from the last tabbable item in the prompt
|
||||
event.preventDefault();
|
||||
tabbables.first().focus();
|
||||
}
|
||||
});
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
outFocus: function() {
|
||||
this.$(tabbableElements.join()).off('keydown');
|
||||
if (this.options.outFocusElement) {
|
||||
this.options.outFocusElement.focus();
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
// public API: show() and hide()
|
||||
show: function() {
|
||||
clearTimeout(this.hideTimeout);
|
||||
this.options.shown = true;
|
||||
this.shownAt = new Date();
|
||||
this.render();
|
||||
if ($.isNumeric(this.options.maxShown)) {
|
||||
this.hideTimeout = setTimeout(_.bind(this.hide, this),
|
||||
this.options.maxShown);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
hide: function() {
|
||||
if (this.shownAt && $.isNumeric(this.options.minShown) &&
|
||||
this.options.minShown > new Date() - this.shownAt) {
|
||||
clearTimeout(this.hideTimeout);
|
||||
this.hideTimeout = setTimeout(_.bind(this.hide, this),
|
||||
this.options.minShown - (new Date() - this.shownAt));
|
||||
} else {
|
||||
this.options.shown = false;
|
||||
delete this.shownAt;
|
||||
this.render();
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
// the rest of the API should be considered semi-private
|
||||
events: {
|
||||
'click .action-close': 'hide',
|
||||
'click .action-primary': 'primaryClick',
|
||||
'click .action-secondary': 'secondaryClick'
|
||||
},
|
||||
|
||||
render: function() {
|
||||
// there can be only one active view of a given type at a time: only
|
||||
// one alert, only one notification, only one prompt. Therefore, we'll
|
||||
// use a singleton approach.
|
||||
var singleton = SystemFeedback['active_' + this.options.type];
|
||||
if (singleton && singleton !== this) {
|
||||
singleton.stopListening();
|
||||
singleton.undelegateEvents();
|
||||
}
|
||||
HtmlUtils.setHtml(this.$el, HtmlUtils.template(systemFeedbackTemplate)(this.options));
|
||||
SystemFeedback['active_' + this.options.type] = this;
|
||||
return this;
|
||||
},
|
||||
|
||||
primaryClick: function(event) {
|
||||
var actions, primary;
|
||||
actions = this.options.actions;
|
||||
if (!actions) { return; }
|
||||
primary = actions.primary;
|
||||
if (!primary) { return; }
|
||||
if (primary.preventDefault !== false) {
|
||||
event.preventDefault();
|
||||
}
|
||||
if (primary.click) {
|
||||
primary.click.call(event.target, this, event);
|
||||
}
|
||||
},
|
||||
|
||||
secondaryClick: function(event) {
|
||||
var actions, secondaryList, secondary, i;
|
||||
actions = this.options.actions;
|
||||
if (!actions) { return; }
|
||||
secondaryList = actions.secondary;
|
||||
if (!secondaryList) { return; }
|
||||
// which secondary action was clicked?
|
||||
i = 0; // default to the first secondary action (easier for testing)
|
||||
if (event && event.target) {
|
||||
i = _.indexOf(this.$('.action-secondary'), event.target);
|
||||
}
|
||||
secondary = secondaryList[i];
|
||||
if (secondary.preventDefault !== false) {
|
||||
event.preventDefault();
|
||||
}
|
||||
if (secondary.click) {
|
||||
secondary.click.call(event.target, this, event);
|
||||
}
|
||||
}
|
||||
});
|
||||
return SystemFeedback;
|
||||
});
|
||||
}).call(this, define || RequireJS.define);
|
||||
|
||||
@@ -18,11 +18,11 @@
|
||||
duration: this.slide_speed
|
||||
});
|
||||
setTimeout(_.bind(SystemFeedbackView.prototype.hide, this, arguments),
|
||||
this.slideSpeed);
|
||||
this.slideSpeed);
|
||||
}
|
||||
});
|
||||
|
||||
// create Alert.Warning, Alert.Confirmation, etc
|
||||
// create Alert.Warning, Alert.Confirmation, etc
|
||||
var capitalCamel, intents;
|
||||
capitalCamel = _.compose(str.capitalize, str.camelize);
|
||||
intents = ['warning', 'error', 'confirmation', 'announcement', 'step-required', 'help', 'mini'];
|
||||
|
||||
@@ -7,73 +7,73 @@
|
||||
'edx-ui-toolkit/js/utils/html-utils',
|
||||
'text!common/templates/components/paging-footer.underscore'
|
||||
],
|
||||
function(_, gettext, Backbone, HtmlUtils, pagingFooterTemplate) {
|
||||
var PagingFooter = Backbone.View.extend({
|
||||
events: {
|
||||
'click .next-page-link': 'nextPage',
|
||||
'click .previous-page-link': 'previousPage',
|
||||
'change .page-number-input': 'changePage'
|
||||
},
|
||||
function(_, gettext, Backbone, HtmlUtils, pagingFooterTemplate) {
|
||||
var PagingFooter = Backbone.View.extend({
|
||||
events: {
|
||||
'click .next-page-link': 'nextPage',
|
||||
'click .previous-page-link': 'previousPage',
|
||||
'change .page-number-input': 'changePage'
|
||||
},
|
||||
|
||||
initialize: function(options) {
|
||||
this.collection = options.collection;
|
||||
this.hideWhenOnePage = options.hideWhenOnePage || false;
|
||||
this.paginationLabel = options.paginationLabel || gettext('Pagination');
|
||||
this.collection.bind('add', _.bind(this.render, this));
|
||||
this.collection.bind('remove', _.bind(this.render, this));
|
||||
this.collection.bind('reset', _.bind(this.render, this));
|
||||
},
|
||||
initialize: function(options) {
|
||||
this.collection = options.collection;
|
||||
this.hideWhenOnePage = options.hideWhenOnePage || false;
|
||||
this.paginationLabel = options.paginationLabel || gettext('Pagination');
|
||||
this.collection.bind('add', _.bind(this.render, this));
|
||||
this.collection.bind('remove', _.bind(this.render, this));
|
||||
this.collection.bind('reset', _.bind(this.render, this));
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var onFirstPage = !this.collection.hasPreviousPage(),
|
||||
onLastPage = !this.collection.hasNextPage();
|
||||
if (this.hideWhenOnePage) {
|
||||
if (this.collection.getTotalPages() <= 1) {
|
||||
this.$el.addClass('hidden');
|
||||
} else if (this.$el.hasClass('hidden')) {
|
||||
this.$el.removeClass('hidden');
|
||||
}
|
||||
render: function() {
|
||||
var onFirstPage = !this.collection.hasPreviousPage(),
|
||||
onLastPage = !this.collection.hasNextPage();
|
||||
if (this.hideWhenOnePage) {
|
||||
if (this.collection.getTotalPages() <= 1) {
|
||||
this.$el.addClass('hidden');
|
||||
} else if (this.$el.hasClass('hidden')) {
|
||||
this.$el.removeClass('hidden');
|
||||
}
|
||||
|
||||
HtmlUtils.setHtml(
|
||||
this.$el,
|
||||
HtmlUtils.template(pagingFooterTemplate)({
|
||||
current_page: this.collection.getPageNumber(),
|
||||
total_pages: this.collection.getTotalPages(),
|
||||
paginationLabel: this.paginationLabel
|
||||
})
|
||||
);
|
||||
this.$('.previous-page-link').toggleClass('is-disabled', onFirstPage).attr('aria-disabled', onFirstPage);
|
||||
this.$('.next-page-link').toggleClass('is-disabled', onLastPage).attr('aria-disabled', onLastPage);
|
||||
return this;
|
||||
},
|
||||
|
||||
changePage: function() {
|
||||
var collection = this.collection,
|
||||
currentPage = collection.getPageNumber(),
|
||||
pageInput = this.$('#page-number-input'),
|
||||
pageNumber = parseInt(pageInput.val(), 10),
|
||||
validInput = true;
|
||||
if (!pageNumber || pageNumber > collection.getTotalPages() || pageNumber < 1) {
|
||||
validInput = false;
|
||||
}
|
||||
// If we still have a page number by this point,
|
||||
// and it's not the current page, load it.
|
||||
if (validInput && pageNumber !== currentPage) {
|
||||
collection.setPage(pageNumber);
|
||||
}
|
||||
pageInput.val(''); // Clear the value as the label will show beneath it
|
||||
},
|
||||
|
||||
nextPage: function() {
|
||||
this.collection.nextPage();
|
||||
},
|
||||
|
||||
previousPage: function() {
|
||||
this.collection.previousPage();
|
||||
}
|
||||
});
|
||||
|
||||
return PagingFooter;
|
||||
}); // end define();
|
||||
HtmlUtils.setHtml(
|
||||
this.$el,
|
||||
HtmlUtils.template(pagingFooterTemplate)({
|
||||
current_page: this.collection.getPageNumber(),
|
||||
total_pages: this.collection.getTotalPages(),
|
||||
paginationLabel: this.paginationLabel
|
||||
})
|
||||
);
|
||||
this.$('.previous-page-link').toggleClass('is-disabled', onFirstPage).attr('aria-disabled', onFirstPage);
|
||||
this.$('.next-page-link').toggleClass('is-disabled', onLastPage).attr('aria-disabled', onLastPage);
|
||||
return this;
|
||||
},
|
||||
|
||||
changePage: function() {
|
||||
var collection = this.collection,
|
||||
currentPage = collection.getPageNumber(),
|
||||
pageInput = this.$('#page-number-input'),
|
||||
pageNumber = parseInt(pageInput.val(), 10),
|
||||
validInput = true;
|
||||
if (!pageNumber || pageNumber > collection.getTotalPages() || pageNumber < 1) {
|
||||
validInput = false;
|
||||
}
|
||||
// If we still have a page number by this point,
|
||||
// and it's not the current page, load it.
|
||||
if (validInput && pageNumber !== currentPage) {
|
||||
collection.setPage(pageNumber);
|
||||
}
|
||||
pageInput.val(''); // Clear the value as the label will show beneath it
|
||||
},
|
||||
|
||||
nextPage: function() {
|
||||
this.collection.nextPage();
|
||||
},
|
||||
|
||||
previousPage: function() {
|
||||
this.collection.previousPage();
|
||||
}
|
||||
});
|
||||
|
||||
return PagingFooter;
|
||||
}); // end define();
|
||||
}).call(this, define || RequireJS.define);
|
||||
|
||||
@@ -13,80 +13,80 @@
|
||||
'edx-ui-toolkit/js/utils/html-utils',
|
||||
'text!common/templates/components/search-field.underscore'
|
||||
],
|
||||
function(Backbone, $, _, HtmlUtils, searchFieldTemplate) {
|
||||
return Backbone.View.extend({
|
||||
function(Backbone, $, _, HtmlUtils, searchFieldTemplate) {
|
||||
return Backbone.View.extend({
|
||||
|
||||
events: {
|
||||
'submit .search-form': 'performSearch',
|
||||
'blur .search-form': 'onFocusOut',
|
||||
'keyup .search-field': 'refreshState',
|
||||
'click .action-clear': 'clearSearch',
|
||||
'mouseover .action-clear': 'setMouseOverState',
|
||||
'mouseout .action-clear': 'setMouseOutState'
|
||||
},
|
||||
events: {
|
||||
'submit .search-form': 'performSearch',
|
||||
'blur .search-form': 'onFocusOut',
|
||||
'keyup .search-field': 'refreshState',
|
||||
'click .action-clear': 'clearSearch',
|
||||
'mouseover .action-clear': 'setMouseOverState',
|
||||
'mouseout .action-clear': 'setMouseOutState'
|
||||
},
|
||||
|
||||
initialize: function(options) {
|
||||
this.type = options.type;
|
||||
this.label = options.label;
|
||||
this.mouseOverClear = false;
|
||||
},
|
||||
initialize: function(options) {
|
||||
this.type = options.type;
|
||||
this.label = options.label;
|
||||
this.mouseOverClear = false;
|
||||
},
|
||||
|
||||
refreshState: function() {
|
||||
var searchField = this.$('.search-field'),
|
||||
clearButton = this.$('.action-clear'),
|
||||
searchString = $.trim(searchField.val());
|
||||
refreshState: function() {
|
||||
var searchField = this.$('.search-field'),
|
||||
clearButton = this.$('.action-clear'),
|
||||
searchString = $.trim(searchField.val());
|
||||
|
||||
if (searchString) {
|
||||
clearButton.removeClass('is-hidden');
|
||||
} else {
|
||||
clearButton.addClass('is-hidden');
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
HtmlUtils.setHtml(
|
||||
this.$el,
|
||||
HtmlUtils.template(searchFieldTemplate)({
|
||||
type: this.type,
|
||||
searchString: this.collection.searchString,
|
||||
searchLabel: this.label
|
||||
})
|
||||
);
|
||||
this.refreshState();
|
||||
return this;
|
||||
},
|
||||
|
||||
setMouseOverState: function(event) {
|
||||
this.mouseOverClear = true;
|
||||
},
|
||||
|
||||
setMouseOutState: function(event) {
|
||||
this.mouseOverClear = false;
|
||||
},
|
||||
|
||||
onFocusOut: function(event) {
|
||||
// If the focus is going anywhere but the clear search
|
||||
// button then treat it as a request to search.
|
||||
if (!this.mouseOverClear) {
|
||||
this.performSearch(event);
|
||||
}
|
||||
},
|
||||
|
||||
performSearch: function(event) {
|
||||
var searchField = this.$('.search-field'),
|
||||
searchString = $.trim(searchField.val());
|
||||
event.preventDefault();
|
||||
this.collection.setSearchString(searchString);
|
||||
return this.collection.refresh();
|
||||
},
|
||||
|
||||
clearSearch: function(event) {
|
||||
event.preventDefault();
|
||||
this.$('.search-field').val('');
|
||||
this.collection.setSearchString('');
|
||||
this.refreshState();
|
||||
return this.collection.refresh();
|
||||
if (searchString) {
|
||||
clearButton.removeClass('is-hidden');
|
||||
} else {
|
||||
clearButton.addClass('is-hidden');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
HtmlUtils.setHtml(
|
||||
this.$el,
|
||||
HtmlUtils.template(searchFieldTemplate)({
|
||||
type: this.type,
|
||||
searchString: this.collection.searchString,
|
||||
searchLabel: this.label
|
||||
})
|
||||
);
|
||||
this.refreshState();
|
||||
return this;
|
||||
},
|
||||
|
||||
setMouseOverState: function(event) {
|
||||
this.mouseOverClear = true;
|
||||
},
|
||||
|
||||
setMouseOutState: function(event) {
|
||||
this.mouseOverClear = false;
|
||||
},
|
||||
|
||||
onFocusOut: function(event) {
|
||||
// If the focus is going anywhere but the clear search
|
||||
// button then treat it as a request to search.
|
||||
if (!this.mouseOverClear) {
|
||||
this.performSearch(event);
|
||||
}
|
||||
},
|
||||
|
||||
performSearch: function(event) {
|
||||
var searchField = this.$('.search-field'),
|
||||
searchString = $.trim(searchField.val());
|
||||
event.preventDefault();
|
||||
this.collection.setSearchString(searchString);
|
||||
return this.collection.refresh();
|
||||
},
|
||||
|
||||
clearSearch: function(event) {
|
||||
event.preventDefault();
|
||||
this.$('.search-field').val('');
|
||||
this.collection.setSearchString('');
|
||||
this.refreshState();
|
||||
return this.collection.refresh();
|
||||
}
|
||||
});
|
||||
});
|
||||
}).call(this, define || RequireJS.define);
|
||||
|
||||
@@ -440,8 +440,8 @@
|
||||
|
||||
DiscussionUtil.stripHighlight = function(htmlString) {
|
||||
return htmlString
|
||||
.replace(/\&(amp\;)?lt\;highlight\&(amp\;)?gt\;/g, '')
|
||||
.replace(/\&(amp\;)?lt\;\/highlight\&(amp\;)?gt\;/g, '');
|
||||
.replace(/\&(amp\;)?lt\;highlight\&(amp\;)?gt\;/g, '')
|
||||
.replace(/\&(amp\;)?lt\;\/highlight\&(amp\;)?gt\;/g, '');
|
||||
};
|
||||
|
||||
DiscussionUtil.stripLatexHighlight = function(htmlSnippet) {
|
||||
|
||||
@@ -505,7 +505,7 @@
|
||||
element,
|
||||
edx.HtmlUtils.joinHtml(
|
||||
edx.HtmlUtils.HTML("<li class='forum-nav-load-more'>"),
|
||||
self.getLoadingContent(gettext('Loading posts list')),
|
||||
self.getLoadingContent(gettext('Loading posts list')),
|
||||
edx.HtmlUtils.HTML('</li>')
|
||||
)
|
||||
);
|
||||
|
||||
@@ -156,9 +156,9 @@
|
||||
view = new PromptView.Confirmation(this.options).show();
|
||||
expect(this.inFocusSpy).toHaveBeenCalled();
|
||||
$('.action-secondary').first().simulate(
|
||||
'keydown',
|
||||
{keyCode: $.simulate.keyCode.TAB}
|
||||
);
|
||||
'keydown',
|
||||
{keyCode: $.simulate.keyCode.TAB}
|
||||
);
|
||||
|
||||
jasmine.waitUntil(function() {
|
||||
return isFocused(view.$('.action-primary'));
|
||||
@@ -169,9 +169,9 @@
|
||||
view = new PromptView.Confirmation(this.options).show();
|
||||
expect(this.inFocusSpy).toHaveBeenCalled();
|
||||
$('.action-primary').first().simulate(
|
||||
'keydown',
|
||||
{keyCode: $.simulate.keyCode.TAB, shiftKey: true}
|
||||
);
|
||||
'keydown',
|
||||
{keyCode: $.simulate.keyCode.TAB, shiftKey: true}
|
||||
);
|
||||
jasmine.waitUntil(function() {
|
||||
return isFocused(view.$('.action-secondary'));
|
||||
}).always(done);
|
||||
|
||||
@@ -17,8 +17,8 @@ define([
|
||||
start: 0,
|
||||
results: _.first(results, perPage)
|
||||
},
|
||||
{parse: true}
|
||||
);
|
||||
{parse: true}
|
||||
);
|
||||
collection.start = 0;
|
||||
collection.totalCount = results.length;
|
||||
return collection;
|
||||
@@ -37,7 +37,7 @@ define([
|
||||
collection: newCollection(20, 5)
|
||||
}).render();
|
||||
expect(pagingHeader.$el.find('.search-count').text())
|
||||
.toContain('Showing 1-5 out of 20 total');
|
||||
.toContain('Showing 1-5 out of 20 total');
|
||||
});
|
||||
|
||||
it('reports that all items are on the current page', function() {
|
||||
@@ -45,7 +45,7 @@ define([
|
||||
collection: newCollection(5, 5)
|
||||
}).render();
|
||||
expect(pagingHeader.$el.find('.search-count').text())
|
||||
.toContain('Showing 1-5 out of 5 total');
|
||||
.toContain('Showing 1-5 out of 5 total');
|
||||
});
|
||||
|
||||
it('reports that the page contains a single item', function() {
|
||||
@@ -53,13 +53,13 @@ define([
|
||||
collection: newCollection(1, 1)
|
||||
}).render();
|
||||
expect(pagingHeader.$el.find('.search-count').text())
|
||||
.toContain('Showing 1 out of 1 total');
|
||||
.toContain('Showing 1 out of 1 total');
|
||||
});
|
||||
|
||||
it('optionally shows sorting controls', function() {
|
||||
pagingHeader = sortableHeader().render();
|
||||
expect(pagingHeader.$el.find('.listing-sort').text())
|
||||
.toMatch(/Sorted by\s+Display Name/);
|
||||
.toMatch(/Sorted by\s+Display Name/);
|
||||
});
|
||||
|
||||
it('does not show sorting controls if the `showSortControls` option is not passed', function() {
|
||||
|
||||
@@ -7,158 +7,158 @@
|
||||
'common/js/components/views/tabbed_view',
|
||||
'jquery.simulate'
|
||||
],
|
||||
function($, _, Backbone, TabbedView) {
|
||||
var keys = $.simulate.keyCode;
|
||||
function($, _, Backbone, TabbedView) {
|
||||
var keys = $.simulate.keyCode;
|
||||
|
||||
var view,
|
||||
TestSubview = Backbone.View.extend({
|
||||
initialize: function(options) {
|
||||
this.text = options.text;
|
||||
},
|
||||
var view,
|
||||
TestSubview = Backbone.View.extend({
|
||||
initialize: function(options) {
|
||||
this.text = options.text;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
this.$el.text(this.text);
|
||||
return this;
|
||||
}
|
||||
}),
|
||||
activeTab = function() {
|
||||
return view.$('.page-content-nav');
|
||||
},
|
||||
activeTabPanel = function() {
|
||||
return view.$('.tabpanel[aria-hidden="false"]');
|
||||
};
|
||||
render: function() {
|
||||
this.$el.text(this.text);
|
||||
return this;
|
||||
}
|
||||
}),
|
||||
activeTab = function() {
|
||||
return view.$('.page-content-nav');
|
||||
},
|
||||
activeTabPanel = function() {
|
||||
return view.$('.tabpanel[aria-hidden="false"]');
|
||||
};
|
||||
|
||||
describe('TabbedView component', function() {
|
||||
beforeEach(function() {
|
||||
view = new TabbedView({
|
||||
tabs: [{
|
||||
title: 'Test 1',
|
||||
view: new TestSubview({text: 'this is test text'}),
|
||||
url: 'test-1'
|
||||
}, {
|
||||
title: 'Test 2',
|
||||
view: new TestSubview({text: 'other text'}),
|
||||
url: 'test-2'
|
||||
}],
|
||||
viewLabel: 'Tabs'
|
||||
}).render();
|
||||
});
|
||||
describe('TabbedView component', function() {
|
||||
beforeEach(function() {
|
||||
view = new TabbedView({
|
||||
tabs: [{
|
||||
title: 'Test 1',
|
||||
view: new TestSubview({text: 'this is test text'}),
|
||||
url: 'test-1'
|
||||
}, {
|
||||
title: 'Test 2',
|
||||
view: new TestSubview({text: 'other text'}),
|
||||
url: 'test-2'
|
||||
}],
|
||||
viewLabel: 'Tabs'
|
||||
}).render();
|
||||
});
|
||||
|
||||
it('can render itself', function() {
|
||||
expect(view.$el.html()).toContain('<div class="page-content-nav"');
|
||||
});
|
||||
it('can render itself', function() {
|
||||
expect(view.$el.html()).toContain('<div class="page-content-nav"');
|
||||
});
|
||||
|
||||
it('shows its first tab by default', function() {
|
||||
expect(activeTabPanel().text()).toContain('this is test text');
|
||||
expect(activeTabPanel().text()).not.toContain('other text');
|
||||
});
|
||||
it('shows its first tab by default', function() {
|
||||
expect(activeTabPanel().text()).toContain('this is test text');
|
||||
expect(activeTabPanel().text()).not.toContain('other text');
|
||||
});
|
||||
|
||||
it('displays titles for each tab', function() {
|
||||
expect(activeTab().text()).toContain('Test 1');
|
||||
expect(activeTab().text()).toContain('Test 2');
|
||||
});
|
||||
it('displays titles for each tab', function() {
|
||||
expect(activeTab().text()).toContain('Test 1');
|
||||
expect(activeTab().text()).toContain('Test 2');
|
||||
});
|
||||
|
||||
it('can switch tabs', function() {
|
||||
view.$('.nav-item[data-index=1]').click();
|
||||
expect(activeTabPanel().text()).not.toContain('this is test text');
|
||||
expect(activeTabPanel().text()).toContain('other text');
|
||||
});
|
||||
it('can switch tabs', function() {
|
||||
view.$('.nav-item[data-index=1]').click();
|
||||
expect(activeTabPanel().text()).not.toContain('this is test text');
|
||||
expect(activeTabPanel().text()).toContain('other text');
|
||||
});
|
||||
|
||||
it('marks the active tab as selected using aria attributes', function() {
|
||||
expect(view.$('.nav-item[data-index=0]')).toHaveAttr({
|
||||
'aria-expanded': 'true',
|
||||
'aria-selected': 'true',
|
||||
tabindex: '0'
|
||||
});
|
||||
expect(view.$('.nav-item[data-index=1]')).toHaveAttr({
|
||||
'aria-expanded': 'false',
|
||||
'aria-selected': 'false',
|
||||
tabindex: '-1'
|
||||
});
|
||||
view.$('.nav-item[data-index=1]').click();
|
||||
expect(view.$('.nav-item[data-index=0]')).toHaveAttr({
|
||||
'aria-expanded': 'false',
|
||||
'aria-selected': 'false',
|
||||
tabindex: '-1'
|
||||
});
|
||||
expect(view.$('.nav-item[data-index=1]')).toHaveAttr({
|
||||
'aria-expanded': 'true',
|
||||
'aria-selected': 'true',
|
||||
tabindex: '0'
|
||||
});
|
||||
});
|
||||
it('marks the active tab as selected using aria attributes', function() {
|
||||
expect(view.$('.nav-item[data-index=0]')).toHaveAttr({
|
||||
'aria-expanded': 'true',
|
||||
'aria-selected': 'true',
|
||||
tabindex: '0'
|
||||
});
|
||||
expect(view.$('.nav-item[data-index=1]')).toHaveAttr({
|
||||
'aria-expanded': 'false',
|
||||
'aria-selected': 'false',
|
||||
tabindex: '-1'
|
||||
});
|
||||
view.$('.nav-item[data-index=1]').click();
|
||||
expect(view.$('.nav-item[data-index=0]')).toHaveAttr({
|
||||
'aria-expanded': 'false',
|
||||
'aria-selected': 'false',
|
||||
tabindex: '-1'
|
||||
});
|
||||
expect(view.$('.nav-item[data-index=1]')).toHaveAttr({
|
||||
'aria-expanded': 'true',
|
||||
'aria-selected': 'true',
|
||||
tabindex: '0'
|
||||
});
|
||||
});
|
||||
|
||||
it('works with keyboard navigation RIGHT and ENTER', function() {
|
||||
view.$('.nav-item[data-index=0]').focus();
|
||||
view.$('.nav-item[data-index=0]')
|
||||
.simulate('keydown', {keyCode: keys.RIGHT})
|
||||
.simulate('keydown', {keyCode: keys.ENTER});
|
||||
it('works with keyboard navigation RIGHT and ENTER', function() {
|
||||
view.$('.nav-item[data-index=0]').focus();
|
||||
view.$('.nav-item[data-index=0]')
|
||||
.simulate('keydown', {keyCode: keys.RIGHT})
|
||||
.simulate('keydown', {keyCode: keys.ENTER});
|
||||
|
||||
expect(view.$('.nav-item[data-index=0]')).toHaveAttr({
|
||||
'aria-expanded': 'false',
|
||||
'aria-selected': 'false',
|
||||
tabindex: '-1'
|
||||
});
|
||||
expect(view.$('.nav-item[data-index=1]')).toHaveAttr({
|
||||
'aria-expanded': 'true',
|
||||
'aria-selected': 'true',
|
||||
tabindex: '0'
|
||||
});
|
||||
});
|
||||
expect(view.$('.nav-item[data-index=0]')).toHaveAttr({
|
||||
'aria-expanded': 'false',
|
||||
'aria-selected': 'false',
|
||||
tabindex: '-1'
|
||||
});
|
||||
expect(view.$('.nav-item[data-index=1]')).toHaveAttr({
|
||||
'aria-expanded': 'true',
|
||||
'aria-selected': 'true',
|
||||
tabindex: '0'
|
||||
});
|
||||
});
|
||||
|
||||
it('works with keyboard navigation DOWN and ENTER', function() {
|
||||
view.$('.nav-item[data-index=0]').focus();
|
||||
view.$('.nav-item[data-index=0]')
|
||||
.simulate('keydown', {keyCode: keys.DOWN})
|
||||
.simulate('keydown', {keyCode: keys.ENTER});
|
||||
it('works with keyboard navigation DOWN and ENTER', function() {
|
||||
view.$('.nav-item[data-index=0]').focus();
|
||||
view.$('.nav-item[data-index=0]')
|
||||
.simulate('keydown', {keyCode: keys.DOWN})
|
||||
.simulate('keydown', {keyCode: keys.ENTER});
|
||||
|
||||
expect(view.$('.nav-item[data-index=0]')).toHaveAttr({
|
||||
'aria-expanded': 'false',
|
||||
'aria-selected': 'false',
|
||||
tabindex: '-1'
|
||||
});
|
||||
expect(view.$('.nav-item[data-index=1]')).toHaveAttr({
|
||||
'aria-expanded': 'true',
|
||||
'aria-selected': 'true',
|
||||
tabindex: '0'
|
||||
});
|
||||
});
|
||||
expect(view.$('.nav-item[data-index=0]')).toHaveAttr({
|
||||
'aria-expanded': 'false',
|
||||
'aria-selected': 'false',
|
||||
tabindex: '-1'
|
||||
});
|
||||
expect(view.$('.nav-item[data-index=1]')).toHaveAttr({
|
||||
'aria-expanded': 'true',
|
||||
'aria-selected': 'true',
|
||||
tabindex: '0'
|
||||
});
|
||||
});
|
||||
|
||||
it('works with keyboard navigation LEFT and ENTER', function() {
|
||||
view.$('.nav-item[data-index=1]').focus();
|
||||
view.$('.nav-item[data-index=1]')
|
||||
.simulate('keydown', {keyCode: keys.LEFT})
|
||||
.simulate('keydown', {keyCode: keys.ENTER});
|
||||
it('works with keyboard navigation LEFT and ENTER', function() {
|
||||
view.$('.nav-item[data-index=1]').focus();
|
||||
view.$('.nav-item[data-index=1]')
|
||||
.simulate('keydown', {keyCode: keys.LEFT})
|
||||
.simulate('keydown', {keyCode: keys.ENTER});
|
||||
|
||||
expect(view.$('.nav-item[data-index=1]')).toHaveAttr({
|
||||
'aria-expanded': 'false',
|
||||
'aria-selected': 'false',
|
||||
tabindex: '-1'
|
||||
});
|
||||
expect(view.$('.nav-item[data-index=0]')).toHaveAttr({
|
||||
'aria-expanded': 'true',
|
||||
'aria-selected': 'true',
|
||||
tabindex: '0'
|
||||
});
|
||||
});
|
||||
expect(view.$('.nav-item[data-index=1]')).toHaveAttr({
|
||||
'aria-expanded': 'false',
|
||||
'aria-selected': 'false',
|
||||
tabindex: '-1'
|
||||
});
|
||||
expect(view.$('.nav-item[data-index=0]')).toHaveAttr({
|
||||
'aria-expanded': 'true',
|
||||
'aria-selected': 'true',
|
||||
tabindex: '0'
|
||||
});
|
||||
});
|
||||
|
||||
it('works with keyboard navigation UP and ENTER', function() {
|
||||
view.$('.nav-item[data-index=1]').focus();
|
||||
view.$('.nav-item[data-index=1]')
|
||||
.simulate('keydown', {keyCode: keys.UP})
|
||||
.simulate('keydown', {keyCode: keys.ENTER});
|
||||
it('works with keyboard navigation UP and ENTER', function() {
|
||||
view.$('.nav-item[data-index=1]').focus();
|
||||
view.$('.nav-item[data-index=1]')
|
||||
.simulate('keydown', {keyCode: keys.UP})
|
||||
.simulate('keydown', {keyCode: keys.ENTER});
|
||||
|
||||
expect(view.$('.nav-item[data-index=1]')).toHaveAttr({
|
||||
'aria-expanded': 'false',
|
||||
'aria-selected': 'false',
|
||||
tabindex: '-1'
|
||||
});
|
||||
expect(view.$('.nav-item[data-index=0]')).toHaveAttr({
|
||||
'aria-expanded': 'true',
|
||||
'aria-selected': 'true',
|
||||
tabindex: '0'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
expect(view.$('.nav-item[data-index=1]')).toHaveAttr({
|
||||
'aria-expanded': 'false',
|
||||
'aria-selected': 'false',
|
||||
tabindex: '-1'
|
||||
});
|
||||
expect(view.$('.nav-item[data-index=0]')).toHaveAttr({
|
||||
'aria-expanded': 'true',
|
||||
'aria-selected': 'true',
|
||||
tabindex: '0'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}).call(this, define || RequireJS.define);
|
||||
|
||||
@@ -2,117 +2,117 @@
|
||||
'use strict';
|
||||
define(['jquery', 'underscore', 'backbone', 'common/js/components/utils/view_utils',
|
||||
'common/js/spec_helpers/view_helpers'],
|
||||
function($, _, Backbone, ViewUtils, ViewHelpers) {
|
||||
describe('ViewUtils', function() {
|
||||
describe('disabled element while running', function() {
|
||||
it("adds 'is-disabled' class to element while action is running and removes it after", function() {
|
||||
var $link,
|
||||
deferred = new $.Deferred(),
|
||||
promise = deferred.promise();
|
||||
setFixtures("<a href='#' id='link'>ripe apples drop about my head</a>");
|
||||
$link = $('#link');
|
||||
expect($link).not.toHaveClass('is-disabled');
|
||||
ViewUtils.disableElementWhileRunning($link, function() { return promise; });
|
||||
expect($link).toHaveClass('is-disabled');
|
||||
deferred.resolve();
|
||||
expect($link).not.toHaveClass('is-disabled');
|
||||
function($, _, Backbone, ViewUtils, ViewHelpers) {
|
||||
describe('ViewUtils', function() {
|
||||
describe('disabled element while running', function() {
|
||||
it("adds 'is-disabled' class to element while action is running and removes it after", function() {
|
||||
var $link,
|
||||
deferred = new $.Deferred(),
|
||||
promise = deferred.promise();
|
||||
setFixtures("<a href='#' id='link'>ripe apples drop about my head</a>");
|
||||
$link = $('#link');
|
||||
expect($link).not.toHaveClass('is-disabled');
|
||||
ViewUtils.disableElementWhileRunning($link, function() { return promise; });
|
||||
expect($link).toHaveClass('is-disabled');
|
||||
deferred.resolve();
|
||||
expect($link).not.toHaveClass('is-disabled');
|
||||
});
|
||||
|
||||
it('disables elements within withDisabledElement', function() {
|
||||
var $link,
|
||||
eventCallback,
|
||||
event,
|
||||
deferred = new $.Deferred(),
|
||||
promise = deferred.promise(),
|
||||
MockView = Backbone.View.extend({
|
||||
testFunction: function() {
|
||||
return promise;
|
||||
}
|
||||
}),
|
||||
testView = new MockView();
|
||||
setFixtures("<a href='#' id='link'>ripe apples drop about my head</a>");
|
||||
$link = $('#link');
|
||||
expect($link).not.toHaveClass('is-disabled');
|
||||
eventCallback = ViewUtils.withDisabledElement('testFunction');
|
||||
event = {currentTarget: $link};
|
||||
eventCallback.apply(testView, [event]);
|
||||
expect($link).toHaveClass('is-disabled');
|
||||
deferred.resolve();
|
||||
expect($link).not.toHaveClass('is-disabled');
|
||||
});
|
||||
});
|
||||
|
||||
describe('progress notification', function() {
|
||||
it('shows progress notification and removes it upon success', function() {
|
||||
var testMessage = 'Testing...',
|
||||
deferred = new $.Deferred(),
|
||||
promise = deferred.promise(),
|
||||
notificationSpy = ViewHelpers.createNotificationSpy();
|
||||
ViewUtils.runOperationShowingMessage(testMessage, function() { return promise; });
|
||||
ViewHelpers.verifyNotificationShowing(notificationSpy, /Testing/);
|
||||
deferred.resolve();
|
||||
ViewHelpers.verifyNotificationHidden(notificationSpy);
|
||||
});
|
||||
|
||||
it('shows progress notification and leaves it showing upon failure', function() {
|
||||
var testMessage = 'Testing...',
|
||||
deferred = new $.Deferred(),
|
||||
promise = deferred.promise(),
|
||||
notificationSpy = ViewHelpers.createNotificationSpy();
|
||||
ViewUtils.runOperationShowingMessage(testMessage, function() { return promise; });
|
||||
ViewHelpers.verifyNotificationShowing(notificationSpy, /Testing/);
|
||||
deferred.fail();
|
||||
ViewHelpers.verifyNotificationShowing(notificationSpy, /Testing/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('course/library fields validation', function() {
|
||||
describe('without unicode support', function() {
|
||||
it('validates presence of field', function() {
|
||||
var error = ViewUtils.validateURLItemEncoding('', false);
|
||||
expect(error).toBeTruthy();
|
||||
});
|
||||
|
||||
it('disables elements within withDisabledElement', function() {
|
||||
var $link,
|
||||
eventCallback,
|
||||
event,
|
||||
deferred = new $.Deferred(),
|
||||
promise = deferred.promise(),
|
||||
MockView = Backbone.View.extend({
|
||||
testFunction: function() {
|
||||
return promise;
|
||||
}
|
||||
}),
|
||||
testView = new MockView();
|
||||
setFixtures("<a href='#' id='link'>ripe apples drop about my head</a>");
|
||||
$link = $('#link');
|
||||
expect($link).not.toHaveClass('is-disabled');
|
||||
eventCallback = ViewUtils.withDisabledElement('testFunction');
|
||||
event = {currentTarget: $link};
|
||||
eventCallback.apply(testView, [event]);
|
||||
expect($link).toHaveClass('is-disabled');
|
||||
deferred.resolve();
|
||||
expect($link).not.toHaveClass('is-disabled');
|
||||
it('checks for presence of special characters in the field', function() {
|
||||
var error;
|
||||
// Special characters are not allowed.
|
||||
error = ViewUtils.validateURLItemEncoding('my+field', false);
|
||||
expect(error).toBeTruthy();
|
||||
error = ViewUtils.validateURLItemEncoding('2014!', false);
|
||||
expect(error).toBeTruthy();
|
||||
error = ViewUtils.validateURLItemEncoding('*field*', false);
|
||||
expect(error).toBeTruthy();
|
||||
// Spaces not allowed.
|
||||
error = ViewUtils.validateURLItemEncoding('Jan 2014', false);
|
||||
expect(error).toBeTruthy();
|
||||
// -_~. are allowed.
|
||||
error = ViewUtils.validateURLItemEncoding('2015-Math_X1.0~', false);
|
||||
expect(error).toBeFalsy();
|
||||
});
|
||||
|
||||
it('does not allow unicode characters', function() {
|
||||
var error = ViewUtils.validateURLItemEncoding('Field-\u010d', false);
|
||||
expect(error).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('progress notification', function() {
|
||||
it('shows progress notification and removes it upon success', function() {
|
||||
var testMessage = 'Testing...',
|
||||
deferred = new $.Deferred(),
|
||||
promise = deferred.promise(),
|
||||
notificationSpy = ViewHelpers.createNotificationSpy();
|
||||
ViewUtils.runOperationShowingMessage(testMessage, function() { return promise; });
|
||||
ViewHelpers.verifyNotificationShowing(notificationSpy, /Testing/);
|
||||
deferred.resolve();
|
||||
ViewHelpers.verifyNotificationHidden(notificationSpy);
|
||||
describe('with unicode support', function() {
|
||||
it('validates presence of field', function() {
|
||||
var error = ViewUtils.validateURLItemEncoding('', true);
|
||||
expect(error).toBeTruthy();
|
||||
});
|
||||
|
||||
it('shows progress notification and leaves it showing upon failure', function() {
|
||||
var testMessage = 'Testing...',
|
||||
deferred = new $.Deferred(),
|
||||
promise = deferred.promise(),
|
||||
notificationSpy = ViewHelpers.createNotificationSpy();
|
||||
ViewUtils.runOperationShowingMessage(testMessage, function() { return promise; });
|
||||
ViewHelpers.verifyNotificationShowing(notificationSpy, /Testing/);
|
||||
deferred.fail();
|
||||
ViewHelpers.verifyNotificationShowing(notificationSpy, /Testing/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('course/library fields validation', function() {
|
||||
describe('without unicode support', function() {
|
||||
it('validates presence of field', function() {
|
||||
var error = ViewUtils.validateURLItemEncoding('', false);
|
||||
expect(error).toBeTruthy();
|
||||
});
|
||||
|
||||
it('checks for presence of special characters in the field', function() {
|
||||
var error;
|
||||
// Special characters are not allowed.
|
||||
error = ViewUtils.validateURLItemEncoding('my+field', false);
|
||||
expect(error).toBeTruthy();
|
||||
error = ViewUtils.validateURLItemEncoding('2014!', false);
|
||||
expect(error).toBeTruthy();
|
||||
error = ViewUtils.validateURLItemEncoding('*field*', false);
|
||||
expect(error).toBeTruthy();
|
||||
// Spaces not allowed.
|
||||
error = ViewUtils.validateURLItemEncoding('Jan 2014', false);
|
||||
expect(error).toBeTruthy();
|
||||
// -_~. are allowed.
|
||||
error = ViewUtils.validateURLItemEncoding('2015-Math_X1.0~', false);
|
||||
expect(error).toBeFalsy();
|
||||
});
|
||||
|
||||
it('does not allow unicode characters', function() {
|
||||
var error = ViewUtils.validateURLItemEncoding('Field-\u010d', false);
|
||||
expect(error).toBeTruthy();
|
||||
});
|
||||
it('checks for presence of spaces', function() {
|
||||
var error = ViewUtils.validateURLItemEncoding('My Field', true);
|
||||
expect(error).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('with unicode support', function() {
|
||||
it('validates presence of field', function() {
|
||||
var error = ViewUtils.validateURLItemEncoding('', true);
|
||||
expect(error).toBeTruthy();
|
||||
});
|
||||
|
||||
it('checks for presence of spaces', function() {
|
||||
var error = ViewUtils.validateURLItemEncoding('My Field', true);
|
||||
expect(error).toBeTruthy();
|
||||
});
|
||||
|
||||
it('allows unicode characters', function() {
|
||||
var error = ViewUtils.validateURLItemEncoding('Field-\u010d', true);
|
||||
expect(error).toBeFalsy();
|
||||
});
|
||||
it('allows unicode characters', function() {
|
||||
var error = ViewUtils.validateURLItemEncoding('Field-\u010d', true);
|
||||
expect(error).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}).call(this, define || RequireJS.define);
|
||||
|
||||
@@ -59,10 +59,10 @@
|
||||
it('fails if a field is provided a value below its minimum character limit', function() {
|
||||
createFixture('text', 'username', false, MIN_LENGTH, MAX_LENGTH, SHORT_STRING);
|
||||
|
||||
// Verify optional field behavior
|
||||
// Verify optional field behavior
|
||||
expectInvalid(MIN_ERROR_FRAGMENT);
|
||||
|
||||
// Verify required field behavior
|
||||
// Verify required field behavior
|
||||
field.prop('required', true);
|
||||
expectInvalid(MIN_ERROR_FRAGMENT);
|
||||
});
|
||||
@@ -70,10 +70,10 @@
|
||||
it('succeeds if a field with no minimum character limit is provided a value below its maximum character limit', function() {
|
||||
createFixture('text', 'username', false, null, MAX_LENGTH, SHORT_STRING);
|
||||
|
||||
// Verify optional field behavior
|
||||
// Verify optional field behavior
|
||||
expectValid();
|
||||
|
||||
// Verify required field behavior
|
||||
// Verify required field behavior
|
||||
field.prop('required', true);
|
||||
expectValid();
|
||||
});
|
||||
@@ -86,10 +86,10 @@
|
||||
it('fails if a field is provided a value above its maximum character limit', function() {
|
||||
createFixture('text', 'username', false, MIN_LENGTH, MAX_LENGTH, LONG_STRING);
|
||||
|
||||
// Verify optional field behavior
|
||||
// Verify optional field behavior
|
||||
expectInvalid(MAX_ERROR_FRAGMENT);
|
||||
|
||||
// Verify required field behavior
|
||||
// Verify required field behavior
|
||||
field.prop('required', true);
|
||||
expectInvalid(MAX_ERROR_FRAGMENT);
|
||||
});
|
||||
@@ -97,10 +97,10 @@
|
||||
it('succeeds if a field with no maximum character limit is provided a value above its minimum character limit', function() {
|
||||
createFixture('text', 'username', false, MIN_LENGTH, null, LONG_STRING);
|
||||
|
||||
// Verify optional field behavior
|
||||
// Verify optional field behavior
|
||||
expectValid();
|
||||
|
||||
// Verify required field behavior
|
||||
// Verify required field behavior
|
||||
field.prop('required', true);
|
||||
expectValid();
|
||||
});
|
||||
@@ -108,10 +108,10 @@
|
||||
it('succeeds if a field with no character limits is provided a value', function() {
|
||||
createFixture('text', 'username', false, null, null, VALID_STRING);
|
||||
|
||||
// Verify optional field behavior
|
||||
// Verify optional field behavior
|
||||
expectValid();
|
||||
|
||||
// Verify required field behavior
|
||||
// Verify required field behavior
|
||||
field.prop('required', true);
|
||||
expectValid();
|
||||
});
|
||||
@@ -119,10 +119,10 @@
|
||||
it('fails if an email field is provided an invalid address', function() {
|
||||
createFixture('email', 'email', false, MIN_LENGTH, MAX_LENGTH, 'localpart');
|
||||
|
||||
// Verify optional field behavior
|
||||
// Verify optional field behavior
|
||||
expectInvalid(EMAIL_ERROR_FRAGMENT);
|
||||
|
||||
// Verify required field behavior
|
||||
// Verify required field behavior
|
||||
field.prop('required', false);
|
||||
expectInvalid(EMAIL_ERROR_FRAGMENT);
|
||||
});
|
||||
@@ -130,10 +130,10 @@
|
||||
it('succeeds if an email field is provided a valid address', function() {
|
||||
createFixture('email', 'email', false, MIN_LENGTH, MAX_LENGTH, 'localpart@label.tld');
|
||||
|
||||
// Verify optional field behavior
|
||||
// Verify optional field behavior
|
||||
expectValid();
|
||||
|
||||
// Verify required field behavior
|
||||
// Verify required field behavior
|
||||
field.prop('required', true);
|
||||
expectValid();
|
||||
});
|
||||
@@ -141,18 +141,18 @@
|
||||
it('succeeds if a checkbox is optional, or required and checked, but fails if a required checkbox is unchecked', function() {
|
||||
createFixture('checkbox', 'checkbox', false, null, null, 'value');
|
||||
|
||||
// Optional, unchecked
|
||||
// Optional, unchecked
|
||||
expectValid();
|
||||
|
||||
// Optional, checked
|
||||
// Optional, checked
|
||||
field.prop('checked', true);
|
||||
expectValid();
|
||||
|
||||
// Required, checked
|
||||
// Required, checked
|
||||
field.prop('required', true);
|
||||
expectValid();
|
||||
|
||||
// Required, unchecked
|
||||
// Required, unchecked
|
||||
field.prop('checked', false);
|
||||
expectInvalid(REQUIRED_ERROR_FRAGMENT);
|
||||
});
|
||||
@@ -170,14 +170,14 @@
|
||||
|
||||
field = $('#dropdown');
|
||||
|
||||
// Optional
|
||||
// Optional
|
||||
expectValid();
|
||||
|
||||
// Required, default text selected
|
||||
// Required, default text selected
|
||||
field.attr('required', true);
|
||||
expectInvalid(REQUIRED_ERROR_FRAGMENT);
|
||||
|
||||
// Required, country selected
|
||||
// Required, country selected
|
||||
field.val('BE');
|
||||
expectValid();
|
||||
});
|
||||
@@ -186,7 +186,7 @@
|
||||
// Create a blank required field
|
||||
createFixture('text', 'username', true, MIN_LENGTH, MAX_LENGTH, '');
|
||||
|
||||
// Attach a custom error message to the field
|
||||
// Attach a custom error message to the field
|
||||
field.data('errormsg-required', CUSTOM_MESSAGE);
|
||||
|
||||
expectInvalid(CUSTOM_MESSAGE);
|
||||
|
||||
@@ -3,149 +3,149 @@
|
||||
*/
|
||||
define(['underscore', 'jquery', 'common/js/components/views/feedback_notification', 'common/js/components/views/feedback_prompt',
|
||||
'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers'],
|
||||
function(_, $, NotificationView, Prompt, AjaxHelpers) {
|
||||
'use strict';
|
||||
var installViewTemplates, createFeedbackSpy, verifyFeedbackShowing,
|
||||
verifyFeedbackHidden, createNotificationSpy, verifyNotificationShowing,
|
||||
verifyNotificationHidden, createPromptSpy, confirmPrompt, inlineEdit, verifyInlineEditChange,
|
||||
installMockAnalytics, removeMockAnalytics, verifyPromptShowing, verifyPromptHidden,
|
||||
clickDeleteItem, patchAndVerifyRequest, submitAndVerifyFormSuccess, submitAndVerifyFormError;
|
||||
function(_, $, NotificationView, Prompt, AjaxHelpers) {
|
||||
'use strict';
|
||||
var installViewTemplates, createFeedbackSpy, verifyFeedbackShowing,
|
||||
verifyFeedbackHidden, createNotificationSpy, verifyNotificationShowing,
|
||||
verifyNotificationHidden, createPromptSpy, confirmPrompt, inlineEdit, verifyInlineEditChange,
|
||||
installMockAnalytics, removeMockAnalytics, verifyPromptShowing, verifyPromptHidden,
|
||||
clickDeleteItem, patchAndVerifyRequest, submitAndVerifyFormSuccess, submitAndVerifyFormError;
|
||||
|
||||
installViewTemplates = function() {
|
||||
appendSetFixtures('<div id="page-notification"></div>');
|
||||
};
|
||||
installViewTemplates = function() {
|
||||
appendSetFixtures('<div id="page-notification"></div>');
|
||||
};
|
||||
|
||||
createFeedbackSpy = function(type, intent) {
|
||||
var feedbackSpy = jasmine.stealth.spyOnConstructor(type, intent, ['show', 'hide']);
|
||||
feedbackSpy.show.and.returnValue(feedbackSpy);
|
||||
if (afterEach) {
|
||||
afterEach(jasmine.stealth.clearSpies);
|
||||
}
|
||||
return feedbackSpy;
|
||||
};
|
||||
createFeedbackSpy = function(type, intent) {
|
||||
var feedbackSpy = jasmine.stealth.spyOnConstructor(type, intent, ['show', 'hide']);
|
||||
feedbackSpy.show.and.returnValue(feedbackSpy);
|
||||
if (afterEach) {
|
||||
afterEach(jasmine.stealth.clearSpies);
|
||||
}
|
||||
return feedbackSpy;
|
||||
};
|
||||
|
||||
verifyFeedbackShowing = function(feedbackSpy, text) {
|
||||
var options;
|
||||
expect(feedbackSpy.constructor).toHaveBeenCalled();
|
||||
expect(feedbackSpy.show).toHaveBeenCalled();
|
||||
expect(feedbackSpy.hide).not.toHaveBeenCalled();
|
||||
options = feedbackSpy.constructor.calls.mostRecent().args[0];
|
||||
expect(options.title).toMatch(text);
|
||||
};
|
||||
verifyFeedbackShowing = function(feedbackSpy, text) {
|
||||
var options;
|
||||
expect(feedbackSpy.constructor).toHaveBeenCalled();
|
||||
expect(feedbackSpy.show).toHaveBeenCalled();
|
||||
expect(feedbackSpy.hide).not.toHaveBeenCalled();
|
||||
options = feedbackSpy.constructor.calls.mostRecent().args[0];
|
||||
expect(options.title).toMatch(text);
|
||||
};
|
||||
|
||||
verifyFeedbackHidden = function(feedbackSpy) {
|
||||
expect(feedbackSpy.hide).toHaveBeenCalled();
|
||||
};
|
||||
verifyFeedbackHidden = function(feedbackSpy) {
|
||||
expect(feedbackSpy.hide).toHaveBeenCalled();
|
||||
};
|
||||
|
||||
createNotificationSpy = function(type) {
|
||||
return createFeedbackSpy(NotificationView, type || 'Mini');
|
||||
};
|
||||
createNotificationSpy = function(type) {
|
||||
return createFeedbackSpy(NotificationView, type || 'Mini');
|
||||
};
|
||||
|
||||
verifyNotificationShowing = function() {
|
||||
verifyFeedbackShowing.apply(this, arguments);
|
||||
};
|
||||
verifyNotificationShowing = function() {
|
||||
verifyFeedbackShowing.apply(this, arguments);
|
||||
};
|
||||
|
||||
verifyNotificationHidden = function() {
|
||||
verifyFeedbackHidden.apply(this, arguments);
|
||||
};
|
||||
verifyNotificationHidden = function() {
|
||||
verifyFeedbackHidden.apply(this, arguments);
|
||||
};
|
||||
|
||||
createPromptSpy = function(type) {
|
||||
return createFeedbackSpy(Prompt, type || 'Warning');
|
||||
};
|
||||
createPromptSpy = function(type) {
|
||||
return createFeedbackSpy(Prompt, type || 'Warning');
|
||||
};
|
||||
|
||||
confirmPrompt = function(promptSpy, pressSecondaryButton) {
|
||||
expect(promptSpy.constructor).toHaveBeenCalled();
|
||||
if (pressSecondaryButton) {
|
||||
promptSpy.constructor.calls.mostRecent().args[0].actions.secondary.click(promptSpy);
|
||||
} else {
|
||||
promptSpy.constructor.calls.mostRecent().args[0].actions.primary.click(promptSpy);
|
||||
}
|
||||
};
|
||||
confirmPrompt = function(promptSpy, pressSecondaryButton) {
|
||||
expect(promptSpy.constructor).toHaveBeenCalled();
|
||||
if (pressSecondaryButton) {
|
||||
promptSpy.constructor.calls.mostRecent().args[0].actions.secondary.click(promptSpy);
|
||||
} else {
|
||||
promptSpy.constructor.calls.mostRecent().args[0].actions.primary.click(promptSpy);
|
||||
}
|
||||
};
|
||||
|
||||
verifyPromptShowing = function() {
|
||||
verifyFeedbackShowing.apply(this, arguments);
|
||||
};
|
||||
verifyPromptShowing = function() {
|
||||
verifyFeedbackShowing.apply(this, arguments);
|
||||
};
|
||||
|
||||
verifyPromptHidden = function() {
|
||||
verifyFeedbackHidden.apply(this, arguments);
|
||||
};
|
||||
verifyPromptHidden = function() {
|
||||
verifyFeedbackHidden.apply(this, arguments);
|
||||
};
|
||||
|
||||
installMockAnalytics = function() {
|
||||
window.analytics = jasmine.createSpyObj('analytics', ['track']);
|
||||
window.course_location_analytics = jasmine.createSpy();
|
||||
};
|
||||
installMockAnalytics = function() {
|
||||
window.analytics = jasmine.createSpyObj('analytics', ['track']);
|
||||
window.course_location_analytics = jasmine.createSpy();
|
||||
};
|
||||
|
||||
removeMockAnalytics = function() {
|
||||
delete window.analytics;
|
||||
delete window.course_location_analytics;
|
||||
};
|
||||
removeMockAnalytics = function() {
|
||||
delete window.analytics;
|
||||
delete window.course_location_analytics;
|
||||
};
|
||||
|
||||
inlineEdit = function(editorWrapper, newValue) {
|
||||
var inputField = editorWrapper.find('.xblock-field-input'),
|
||||
editButton = editorWrapper.find('.xblock-field-value-edit');
|
||||
editButton.click();
|
||||
inlineEdit = function(editorWrapper, newValue) {
|
||||
var inputField = editorWrapper.find('.xblock-field-input'),
|
||||
editButton = editorWrapper.find('.xblock-field-value-edit');
|
||||
editButton.click();
|
||||
expect(editorWrapper).toHaveClass('is-editing');
|
||||
inputField.val(newValue);
|
||||
return inputField;
|
||||
};
|
||||
|
||||
verifyInlineEditChange = function(editorWrapper, expectedValue, failedValue) {
|
||||
var displayName = editorWrapper.find('.xblock-field-value');
|
||||
expect(displayName.text()).toBe(expectedValue);
|
||||
if (failedValue) {
|
||||
expect(editorWrapper).toHaveClass('is-editing');
|
||||
inputField.val(newValue);
|
||||
return inputField;
|
||||
};
|
||||
} else {
|
||||
expect(editorWrapper).not.toHaveClass('is-editing');
|
||||
}
|
||||
};
|
||||
|
||||
verifyInlineEditChange = function(editorWrapper, expectedValue, failedValue) {
|
||||
var displayName = editorWrapper.find('.xblock-field-value');
|
||||
expect(displayName.text()).toBe(expectedValue);
|
||||
if (failedValue) {
|
||||
expect(editorWrapper).toHaveClass('is-editing');
|
||||
} else {
|
||||
expect(editorWrapper).not.toHaveClass('is-editing');
|
||||
}
|
||||
};
|
||||
clickDeleteItem = function(that, promptSpy, promptText) {
|
||||
that.view.$('.delete').click();
|
||||
verifyPromptShowing(promptSpy, promptText);
|
||||
confirmPrompt(promptSpy);
|
||||
verifyPromptHidden(promptSpy);
|
||||
};
|
||||
|
||||
clickDeleteItem = function(that, promptSpy, promptText) {
|
||||
that.view.$('.delete').click();
|
||||
verifyPromptShowing(promptSpy, promptText);
|
||||
confirmPrompt(promptSpy);
|
||||
verifyPromptHidden(promptSpy);
|
||||
};
|
||||
patchAndVerifyRequest = function(requests, url, notificationSpy) {
|
||||
// Backbone.emulateHTTP is enabled in our system, so setting this
|
||||
// option will fake PUT, PATCH and DELETE requests with a HTTP POST,
|
||||
// setting the X-HTTP-Method-Override header with the true method.
|
||||
AjaxHelpers.expectJsonRequest(requests, 'POST', url);
|
||||
expect(_.last(requests).requestHeaders['X-HTTP-Method-Override']).toBe('DELETE');
|
||||
verifyNotificationShowing(notificationSpy, /Deleting/);
|
||||
};
|
||||
|
||||
patchAndVerifyRequest = function(requests, url, notificationSpy) {
|
||||
// Backbone.emulateHTTP is enabled in our system, so setting this
|
||||
// option will fake PUT, PATCH and DELETE requests with a HTTP POST,
|
||||
// setting the X-HTTP-Method-Override header with the true method.
|
||||
AjaxHelpers.expectJsonRequest(requests, 'POST', url);
|
||||
expect(_.last(requests).requestHeaders['X-HTTP-Method-Override']).toBe('DELETE');
|
||||
verifyNotificationShowing(notificationSpy, /Deleting/);
|
||||
};
|
||||
submitAndVerifyFormSuccess = function(view, requests, notificationSpy) {
|
||||
view.$('form').submit();
|
||||
verifyNotificationShowing(notificationSpy, /Saving/);
|
||||
AjaxHelpers.respondWithJson(requests, {});
|
||||
verifyNotificationHidden(notificationSpy);
|
||||
};
|
||||
|
||||
submitAndVerifyFormSuccess = function(view, requests, notificationSpy) {
|
||||
view.$('form').submit();
|
||||
verifyNotificationShowing(notificationSpy, /Saving/);
|
||||
AjaxHelpers.respondWithJson(requests, {});
|
||||
verifyNotificationHidden(notificationSpy);
|
||||
};
|
||||
submitAndVerifyFormError = function(view, requests, notificationSpy) {
|
||||
view.$('form').submit();
|
||||
verifyNotificationShowing(notificationSpy, /Saving/);
|
||||
AjaxHelpers.respondWithError(requests);
|
||||
verifyNotificationShowing(notificationSpy, /Saving/);
|
||||
};
|
||||
|
||||
submitAndVerifyFormError = function(view, requests, notificationSpy) {
|
||||
view.$('form').submit();
|
||||
verifyNotificationShowing(notificationSpy, /Saving/);
|
||||
AjaxHelpers.respondWithError(requests);
|
||||
verifyNotificationShowing(notificationSpy, /Saving/);
|
||||
};
|
||||
|
||||
return {
|
||||
installViewTemplates: installViewTemplates,
|
||||
createNotificationSpy: createNotificationSpy,
|
||||
verifyNotificationShowing: verifyNotificationShowing,
|
||||
verifyNotificationHidden: verifyNotificationHidden,
|
||||
confirmPrompt: confirmPrompt,
|
||||
createPromptSpy: createPromptSpy,
|
||||
verifyPromptShowing: verifyPromptShowing,
|
||||
verifyPromptHidden: verifyPromptHidden,
|
||||
inlineEdit: inlineEdit,
|
||||
verifyInlineEditChange: verifyInlineEditChange,
|
||||
installMockAnalytics: installMockAnalytics,
|
||||
removeMockAnalytics: removeMockAnalytics,
|
||||
clickDeleteItem: clickDeleteItem,
|
||||
patchAndVerifyRequest: patchAndVerifyRequest,
|
||||
submitAndVerifyFormSuccess: submitAndVerifyFormSuccess,
|
||||
submitAndVerifyFormError: submitAndVerifyFormError
|
||||
};
|
||||
}
|
||||
return {
|
||||
installViewTemplates: installViewTemplates,
|
||||
createNotificationSpy: createNotificationSpy,
|
||||
verifyNotificationShowing: verifyNotificationShowing,
|
||||
verifyNotificationHidden: verifyNotificationHidden,
|
||||
confirmPrompt: confirmPrompt,
|
||||
createPromptSpy: createPromptSpy,
|
||||
verifyPromptShowing: verifyPromptShowing,
|
||||
verifyPromptHidden: verifyPromptHidden,
|
||||
inlineEdit: inlineEdit,
|
||||
verifyInlineEditChange: verifyInlineEditChange,
|
||||
installMockAnalytics: installMockAnalytics,
|
||||
removeMockAnalytics: removeMockAnalytics,
|
||||
clickDeleteItem: clickDeleteItem,
|
||||
patchAndVerifyRequest: patchAndVerifyRequest,
|
||||
submitAndVerifyFormSuccess: submitAndVerifyFormSuccess,
|
||||
submitAndVerifyFormError: submitAndVerifyFormError
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
'underscore.string',
|
||||
'gettext'
|
||||
],
|
||||
function($, _, _s, gettext) {
|
||||
var utils;
|
||||
function($, _, _s, gettext) {
|
||||
var utils;
|
||||
|
||||
/* Mix non-conflicting functions from underscore.string
|
||||
* (all but include, contains, and reverse) into the
|
||||
@@ -15,96 +15,96 @@
|
||||
* by the access view, but doing it here helps keep the
|
||||
* utility self-contained.
|
||||
*/
|
||||
_.mixin(_s.exports());
|
||||
_.mixin(_s.exports());
|
||||
|
||||
utils = (function() {
|
||||
var _fn = {
|
||||
validate: {
|
||||
utils = (function() {
|
||||
var _fn = {
|
||||
validate: {
|
||||
|
||||
template: _.template('<li <%- suppressAttr %>><%- content %></li>'),
|
||||
template: _.template('<li <%- suppressAttr %>><%- content %></li>'),
|
||||
|
||||
msg: {
|
||||
email: gettext("The email address you've provided isn't formatted correctly."),
|
||||
min: gettext('%(field)s must have at least %(count)d characters.'),
|
||||
max: gettext('%(field)s can only contain up to %(count)d characters.'),
|
||||
required: gettext('Please enter your %(field)s.')
|
||||
},
|
||||
msg: {
|
||||
email: gettext("The email address you've provided isn't formatted correctly."),
|
||||
min: gettext('%(field)s must have at least %(count)d characters.'),
|
||||
max: gettext('%(field)s can only contain up to %(count)d characters.'),
|
||||
required: gettext('Please enter your %(field)s.')
|
||||
},
|
||||
|
||||
field: function(el) {
|
||||
var $el = $(el),
|
||||
required = true,
|
||||
min = true,
|
||||
max = true,
|
||||
email = true,
|
||||
response = {},
|
||||
isBlank = _fn.validate.isBlank($el);
|
||||
field: function(el) {
|
||||
var $el = $(el),
|
||||
required = true,
|
||||
min = true,
|
||||
max = true,
|
||||
email = true,
|
||||
response = {},
|
||||
isBlank = _fn.validate.isBlank($el);
|
||||
|
||||
if (_fn.validate.isRequired($el)) {
|
||||
if (isBlank) {
|
||||
required = false;
|
||||
} else {
|
||||
min = _fn.validate.str.minlength($el);
|
||||
max = _fn.validate.str.maxlength($el);
|
||||
email = _fn.validate.email.valid($el);
|
||||
}
|
||||
} else if (!isBlank) {
|
||||
if (_fn.validate.isRequired($el)) {
|
||||
if (isBlank) {
|
||||
required = false;
|
||||
} else {
|
||||
min = _fn.validate.str.minlength($el);
|
||||
max = _fn.validate.str.maxlength($el);
|
||||
email = _fn.validate.email.valid($el);
|
||||
}
|
||||
} else if (!isBlank) {
|
||||
min = _fn.validate.str.minlength($el);
|
||||
max = _fn.validate.str.maxlength($el);
|
||||
email = _fn.validate.email.valid($el);
|
||||
}
|
||||
|
||||
response.isValid = required && min && max && email;
|
||||
response.isValid = required && min && max && email;
|
||||
|
||||
if (!response.isValid) {
|
||||
_fn.validate.removeDefault($el);
|
||||
if (!response.isValid) {
|
||||
_fn.validate.removeDefault($el);
|
||||
|
||||
response.message = _fn.validate.getMessage($el, {
|
||||
required: required,
|
||||
min: min,
|
||||
max: max,
|
||||
email: email
|
||||
});
|
||||
}
|
||||
response.message = _fn.validate.getMessage($el, {
|
||||
required: required,
|
||||
min: min,
|
||||
max: max,
|
||||
email: email
|
||||
});
|
||||
}
|
||||
|
||||
return response;
|
||||
return response;
|
||||
},
|
||||
|
||||
str: {
|
||||
minlength: function($el) {
|
||||
var min = $el.attr('minlength') || 0;
|
||||
|
||||
return min <= $el.val().length;
|
||||
},
|
||||
|
||||
str: {
|
||||
minlength: function($el) {
|
||||
var min = $el.attr('minlength') || 0;
|
||||
maxlength: function($el) {
|
||||
var max = $el.attr('maxlength') || false;
|
||||
|
||||
return min <= $el.val().length;
|
||||
},
|
||||
return (!!max) ? max >= $el.val().length : true;
|
||||
}
|
||||
},
|
||||
|
||||
maxlength: function($el) {
|
||||
var max = $el.attr('maxlength') || false;
|
||||
isRequired: function($el) {
|
||||
return $el.attr('required');
|
||||
},
|
||||
|
||||
return (!!max) ? max >= $el.val().length : true;
|
||||
}
|
||||
},
|
||||
isBlank: function($el) {
|
||||
var type = $el.attr('type'),
|
||||
isBlank;
|
||||
|
||||
isRequired: function($el) {
|
||||
return $el.attr('required');
|
||||
},
|
||||
if (type === 'checkbox') {
|
||||
isBlank = !$el.prop('checked');
|
||||
} else if (type === 'select') {
|
||||
isBlank = ($el.data('isdefault') === true);
|
||||
} else {
|
||||
isBlank = !$el.val();
|
||||
}
|
||||
|
||||
isBlank: function($el) {
|
||||
var type = $el.attr('type'),
|
||||
isBlank;
|
||||
return isBlank;
|
||||
},
|
||||
|
||||
if (type === 'checkbox') {
|
||||
isBlank = !$el.prop('checked');
|
||||
} else if (type === 'select') {
|
||||
isBlank = ($el.data('isdefault') === true);
|
||||
} else {
|
||||
isBlank = !$el.val();
|
||||
}
|
||||
|
||||
return isBlank;
|
||||
},
|
||||
|
||||
email: {
|
||||
email: {
|
||||
// This is the same regex used to validate email addresses in Django 1.11
|
||||
regex: new RegExp(
|
||||
regex: new RegExp(
|
||||
[
|
||||
'(^[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+(\\.[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+)*',
|
||||
'|^"([\\001-\\010\\013\\014\\016-\\037!#-\\[\\]-\\177]|\\\\[\\001-\\011\\013\\014\\016-\\177])*"', // eslint-disable-line max-len
|
||||
@@ -113,82 +113,82 @@
|
||||
].join(''), 'i'
|
||||
),
|
||||
|
||||
valid: function($el) {
|
||||
return $el.attr('type') === 'email' ? _fn.validate.email.format($el.val()) : true;
|
||||
},
|
||||
|
||||
format: function(str) {
|
||||
return _fn.validate.email.regex.test(str);
|
||||
}
|
||||
valid: function($el) {
|
||||
return $el.attr('type') === 'email' ? _fn.validate.email.format($el.val()) : true;
|
||||
},
|
||||
|
||||
getLabel: function(id) {
|
||||
format: function(str) {
|
||||
return _fn.validate.email.regex.test(str);
|
||||
}
|
||||
},
|
||||
|
||||
getLabel: function(id) {
|
||||
// Extract the field label, remove the asterisk (if it appears) and any extra whitespace
|
||||
return $('label[for=' + id + '] > span.label-text').text().split('*')[0].trim();
|
||||
},
|
||||
return $('label[for=' + id + '] > span.label-text').text().split('*')[0].trim();
|
||||
},
|
||||
|
||||
getMessage: function($el, tests) {
|
||||
var txt = [],
|
||||
label,
|
||||
context,
|
||||
content,
|
||||
customMsg,
|
||||
liveValidationMsg,
|
||||
suppressAttr;
|
||||
getMessage: function($el, tests) {
|
||||
var txt = [],
|
||||
label,
|
||||
context,
|
||||
content,
|
||||
customMsg,
|
||||
liveValidationMsg,
|
||||
suppressAttr;
|
||||
|
||||
_.each(tests, function(value, key) {
|
||||
if (!value) {
|
||||
label = _fn.validate.getLabel($el.attr('id'));
|
||||
customMsg = $el.data('errormsg-' + key) || false;
|
||||
liveValidationMsg =
|
||||
_.each(tests, function(value, key) {
|
||||
if (!value) {
|
||||
label = _fn.validate.getLabel($el.attr('id'));
|
||||
customMsg = $el.data('errormsg-' + key) || false;
|
||||
liveValidationMsg =
|
||||
$('#' + $el.attr('id') + '-validation-error-msg').text() || false;
|
||||
|
||||
// If the field has a custom error msg attached, use it
|
||||
if (customMsg) {
|
||||
content = customMsg;
|
||||
} else if (liveValidationMsg) {
|
||||
content = liveValidationMsg;
|
||||
} else {
|
||||
context = {field: label};
|
||||
if (customMsg) {
|
||||
content = customMsg;
|
||||
} else if (liveValidationMsg) {
|
||||
content = liveValidationMsg;
|
||||
} else {
|
||||
context = {field: label};
|
||||
|
||||
if (key === 'min') {
|
||||
context.count = parseInt($el.attr('minlength'), 10);
|
||||
} else if (key === 'max') {
|
||||
context.count = parseInt($el.attr('maxlength'), 10);
|
||||
}
|
||||
|
||||
content = _.sprintf(_fn.validate.msg[key], context);
|
||||
if (key === 'min') {
|
||||
context.count = parseInt($el.attr('minlength'), 10);
|
||||
} else if (key === 'max') {
|
||||
context.count = parseInt($el.attr('maxlength'), 10);
|
||||
}
|
||||
|
||||
suppressAttr = '';
|
||||
if (['username', 'email'].indexOf($el.attr('name')) > -1) {
|
||||
suppressAttr = 'data-hj-suppress';
|
||||
}
|
||||
|
||||
txt.push(_fn.validate.template({
|
||||
content: content,
|
||||
suppressAttr: suppressAttr
|
||||
}));
|
||||
content = _.sprintf(_fn.validate.msg[key], context);
|
||||
}
|
||||
});
|
||||
|
||||
return txt.join(' ');
|
||||
},
|
||||
suppressAttr = '';
|
||||
if (['username', 'email'].indexOf($el.attr('name')) > -1) {
|
||||
suppressAttr = 'data-hj-suppress';
|
||||
}
|
||||
|
||||
txt.push(_fn.validate.template({
|
||||
content: content,
|
||||
suppressAttr: suppressAttr
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
return txt.join(' ');
|
||||
},
|
||||
|
||||
// Removes the default HTML5 validation pop-up
|
||||
removeDefault: function($el) {
|
||||
if ($el.setCustomValidity) {
|
||||
$el.setCustomValidity(' ');
|
||||
}
|
||||
removeDefault: function($el) {
|
||||
if ($el.setCustomValidity) {
|
||||
$el.setCustomValidity(' ');
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
validate: _fn.validate.field
|
||||
};
|
||||
}());
|
||||
return {
|
||||
validate: _fn.validate.field
|
||||
};
|
||||
}());
|
||||
|
||||
return utils;
|
||||
});
|
||||
return utils;
|
||||
});
|
||||
}).call(this, define || RequireJS.define);
|
||||
|
||||
@@ -5,11 +5,11 @@ Helper function used to require files serially instead of concurrently.
|
||||
'use strict';
|
||||
|
||||
var requireModules = function(paths, callback, modules) {
|
||||
// If all the modules have been loaded, call the callback.
|
||||
// If all the modules have been loaded, call the callback.
|
||||
if (paths.length === 0) {
|
||||
return callback.apply(null, modules);
|
||||
}
|
||||
// Otherwise load the next one.
|
||||
// Otherwise load the next one.
|
||||
require([paths.shift()], function(module) {
|
||||
modules.push(module);
|
||||
requireModules(paths, callback, modules);
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
var input_field = problem.find('input[type=hidden]');
|
||||
var protex_answer = protexCheckAnswer();
|
||||
var value = {protex_answer: protex_answer};
|
||||
// console.log(JSON.stringify(value));
|
||||
// console.log(JSON.stringify(value));
|
||||
input_field.val(JSON.stringify(value));
|
||||
}
|
||||
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
requirejs.config({baseUrl: baseUrl});
|
||||
}
|
||||
|
||||
// The current JS file will be loaded and run each time. It will require a
|
||||
// single dependency which will be loaded and stored by RequireJS. On
|
||||
// subsequent runs, RequireJS will return the dependency from memory, rather
|
||||
// than loading it again from the server. For that reason, it is a good idea to
|
||||
// keep the current JS file as small as possible, and move everything else into
|
||||
// RequireJS module dependencies.
|
||||
// The current JS file will be loaded and run each time. It will require a
|
||||
// single dependency which will be loaded and stored by RequireJS. On
|
||||
// subsequent runs, RequireJS will return the dependency from memory, rather
|
||||
// than loading it again from the server. For that reason, it is a good idea to
|
||||
// keep the current JS file as small as possible, and move everything else into
|
||||
// RequireJS module dependencies.
|
||||
require(['js/capa/drag_and_drop/main'], function(Main) {
|
||||
Main();
|
||||
});
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
state.baseImageEl.error(function() {
|
||||
var errorMsg = HtmlUtils.joinHtml(
|
||||
HtmlUtils.HTML('<span style="color: red;">'),
|
||||
HtmlUtils.HTML('ERROR: Image "'), state.config.baseImage, HtmlUtils.HTML('" was not found!'),
|
||||
HtmlUtils.HTML('ERROR: Image "'), state.config.baseImage, HtmlUtils.HTML('" was not found!'),
|
||||
HtmlUtils.HTML('</span>')
|
||||
);
|
||||
console.log('ERROR: Image "' + state.config.baseImage + '" was not found!');
|
||||
|
||||
@@ -39,11 +39,11 @@
|
||||
if (processDraggable(state, draggable) !== true) {
|
||||
state.config.foundErrors = true;
|
||||
|
||||
// Exit immediately from .every() call.
|
||||
// Exit immediately from .every() call.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Continue to next .every() call.
|
||||
// Continue to next .every() call.
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
@@ -75,11 +75,11 @@
|
||||
if (processTarget(state, target) !== true) {
|
||||
state.config.foundErrors = true;
|
||||
|
||||
// Exit immediately from .every() call.
|
||||
// Exit immediately from .every() call.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Continue to next .every() call.
|
||||
// Continue to next .every() call.
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
@@ -151,24 +151,24 @@
|
||||
|
||||
function processDraggable(state, obj) {
|
||||
if (
|
||||
(attrIsString(obj, 'id') === false) ||
|
||||
(attrIsString(obj, 'id') === false) ||
|
||||
(attrIsString(obj, 'icon') === false) ||
|
||||
(attrIsString(obj, 'label') === false) ||
|
||||
|
||||
(attrIsBoolean(obj, 'can_reuse', false) === false) ||
|
||||
|
||||
(obj.hasOwnProperty('target_fields') === false)
|
||||
) {
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that all targets in the 'target_fields' property are proper target objects.
|
||||
// We will be testing the return value from .every() call (it can be 'true' or 'false').
|
||||
// Check that all targets in the 'target_fields' property are proper target objects.
|
||||
// We will be testing the return value from .every() call (it can be 'true' or 'false').
|
||||
if (obj.target_fields.every(
|
||||
function(targetObj) {
|
||||
return processTarget(state, targetObj, false);
|
||||
}
|
||||
) === false) {
|
||||
function(targetObj) {
|
||||
return processTarget(state, targetObj, false);
|
||||
}
|
||||
) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -177,23 +177,23 @@
|
||||
return true;
|
||||
}
|
||||
|
||||
// We need 'pushToState' parameter in order to simply test an object for the fact that it is a
|
||||
// proper target (without pushing it to the 'state' object). When
|
||||
//
|
||||
// pushToState === false
|
||||
//
|
||||
// the object being tested is not going to be pushed to 'state'. The function will onyl return
|
||||
// 'true' or 'false.
|
||||
// We need 'pushToState' parameter in order to simply test an object for the fact that it is a
|
||||
// proper target (without pushing it to the 'state' object). When
|
||||
//
|
||||
// pushToState === false
|
||||
//
|
||||
// the object being tested is not going to be pushed to 'state'. The function will onyl return
|
||||
// 'true' or 'false.
|
||||
function processTarget(state, obj, pushToState) {
|
||||
if (
|
||||
(attrIsString(obj, 'id') === false) ||
|
||||
(attrIsString(obj, 'id') === false) ||
|
||||
|
||||
(attrIsInteger(obj, 'w') === false) ||
|
||||
(attrIsInteger(obj, 'h') === false) ||
|
||||
|
||||
(attrIsInteger(obj, 'x') === false) ||
|
||||
(attrIsInteger(obj, 'y') === false)
|
||||
) {
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
|
||||
mouseDown: function(event) {
|
||||
if (this.mousePressed === false) {
|
||||
// So that the browser does not perform a default drag.
|
||||
// If we don't do this, each drag operation will
|
||||
// potentially cause the highlghting of the dragged element.
|
||||
// So that the browser does not perform a default drag.
|
||||
// If we don't do this, each drag operation will
|
||||
// potentially cause the highlghting of the dragged element.
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
@@ -29,8 +29,8 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// If this draggable is just being dragged out of the
|
||||
// container, we must perform some additional tasks.
|
||||
// If this draggable is just being dragged out of the
|
||||
// container, we must perform some additional tasks.
|
||||
if (this.inContainer === true) {
|
||||
if ((this.isReusable === true) && (this.isOriginal === true)) {
|
||||
this.makeDraggableCopy(function(draggableCopy) {
|
||||
@@ -82,7 +82,7 @@
|
||||
if (this.isOriginal === true) {
|
||||
this.state.numDraggablesInSlider -= 1;
|
||||
}
|
||||
// SR: global "screen reader" object in accessibility_tools.js
|
||||
// SR: global "screen reader" object in accessibility_tools.js
|
||||
window.SR.readText(gettext('dragging out of slider'));
|
||||
} else {
|
||||
window.SR.readText(gettext('dragging'));
|
||||
@@ -117,12 +117,12 @@
|
||||
|
||||
mouseMove: function(event) {
|
||||
if (this.mousePressed === true) {
|
||||
// Because we have also attached a 'mousemove' event to the
|
||||
// 'document' (that will do the same thing), let's tell the
|
||||
// browser not to bubble up this event. The attached event
|
||||
// on the 'document' will only be triggered when the mouse
|
||||
// pointer leaves the draggable while it is in the middle
|
||||
// of a drag operation (user moves the mouse very quickly).
|
||||
// Because we have also attached a 'mousemove' event to the
|
||||
// 'document' (that will do the same thing), let's tell the
|
||||
// browser not to bubble up this event. The attached event
|
||||
// on the 'document' will only be triggered when the mouse
|
||||
// pointer leaves the draggable while it is in the middle
|
||||
// of a drag operation (user moves the mouse very quickly).
|
||||
event.stopPropagation();
|
||||
|
||||
this.iconEl.css({
|
||||
|
||||
@@ -108,11 +108,11 @@
|
||||
}
|
||||
},
|
||||
|
||||
// At this point the mouse was realeased, and we need to check
|
||||
// where the draggable eneded up. Based on several things, we
|
||||
// will either move the draggable back to the slider, or update
|
||||
// the input with the user's answer (X-Y position of the draggable,
|
||||
// or the ID of the target where it landed.
|
||||
// At this point the mouse was realeased, and we need to check
|
||||
// where the draggable eneded up. Based on several things, we
|
||||
// will either move the draggable back to the slider, or update
|
||||
// the input with the user's answer (X-Y position of the draggable,
|
||||
// or the ID of the target where it landed.
|
||||
checkLandingElement: function() {
|
||||
var positionIE;
|
||||
|
||||
@@ -137,11 +137,11 @@
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
(positionIE.left < 0) ||
|
||||
(positionIE.left < 0) ||
|
||||
(positionIE.left + this.iconWidth > this.state.baseImageEl.width()) ||
|
||||
(positionIE.top < 0) ||
|
||||
(positionIE.top + this.iconHeight > this.state.baseImageEl.height())
|
||||
) {
|
||||
) {
|
||||
this.moveBackToSlider();
|
||||
|
||||
this.x = -1;
|
||||
@@ -166,73 +166,73 @@
|
||||
updateInput.update(this.state);
|
||||
},
|
||||
|
||||
// Determine if a draggable, after it was relased, ends up on a
|
||||
// target. We do this by iterating over all of the targets, and
|
||||
// for each one we check whether the draggable's center is
|
||||
// within the target's dimensions.
|
||||
//
|
||||
// positionIE is the object as returned by
|
||||
//
|
||||
// this.iconEl.position()
|
||||
// Determine if a draggable, after it was relased, ends up on a
|
||||
// target. We do this by iterating over all of the targets, and
|
||||
// for each one we check whether the draggable's center is
|
||||
// within the target's dimensions.
|
||||
//
|
||||
// positionIE is the object as returned by
|
||||
//
|
||||
// this.iconEl.position()
|
||||
checkIfOnTarget: function(positionIE) {
|
||||
var c1, target;
|
||||
|
||||
for (c1 = 0; c1 < this.state.targets.length; c1 += 1) {
|
||||
target = this.state.targets[c1];
|
||||
|
||||
// If only one draggable per target is allowed, and
|
||||
// the current target already has a draggable on it
|
||||
// (with an ID different from the one we are checking
|
||||
// against), then go to next target.
|
||||
// If only one draggable per target is allowed, and
|
||||
// the current target already has a draggable on it
|
||||
// (with an ID different from the one we are checking
|
||||
// against), then go to next target.
|
||||
if (
|
||||
(this.state.config.onePerTarget === true) &&
|
||||
(this.state.config.onePerTarget === true) &&
|
||||
(target.draggableList.length === 1) &&
|
||||
(target.draggableList[0].uniqueId !== this.uniqueId)
|
||||
) {
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the target is on a draggable (from target field), we must make sure that
|
||||
// this draggable is not the same as "this" one.
|
||||
// If the target is on a draggable (from target field), we must make sure that
|
||||
// this draggable is not the same as "this" one.
|
||||
if ((target.type === 'on_drag') && (target.draggableObj.uniqueId === this.uniqueId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the draggable's center coordinate is within
|
||||
// the target's dimensions. If not, go to next target.
|
||||
// Check if the draggable's center coordinate is within
|
||||
// the target's dimensions. If not, go to next target.
|
||||
if (
|
||||
(positionIE.top + this.iconHeight * 0.5 < target.offset.top) ||
|
||||
(positionIE.top + this.iconHeight * 0.5 < target.offset.top) ||
|
||||
(positionIE.top + this.iconHeight * 0.5 > target.offset.top + target.h) ||
|
||||
(positionIE.left + this.iconWidth * 0.5 < target.offset.left) ||
|
||||
(positionIE.left + this.iconWidth * 0.5 > target.offset.left + target.w)
|
||||
) {
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the draggable was moved from one target to
|
||||
// another, then we need to remove it from the
|
||||
// previous target's draggables list, and add it to the
|
||||
// new target's draggables list.
|
||||
// If the draggable was moved from one target to
|
||||
// another, then we need to remove it from the
|
||||
// previous target's draggables list, and add it to the
|
||||
// new target's draggables list.
|
||||
if ((this.onTarget !== null) && (this.onTarget.uniqueId !== target.uniqueId)) {
|
||||
this.onTarget.removeDraggable(this);
|
||||
target.addDraggable(this);
|
||||
}
|
||||
// If the draggable was moved from the slider to a
|
||||
// target, remember the target, and add ID to the
|
||||
// target's draggables list.
|
||||
// If the draggable was moved from the slider to a
|
||||
// target, remember the target, and add ID to the
|
||||
// target's draggables list.
|
||||
else if (this.onTarget === null) {
|
||||
target.addDraggable(this);
|
||||
}
|
||||
|
||||
// Reposition the draggable so that it's center
|
||||
// coincides with the center of the target.
|
||||
// Reposition the draggable so that it's center
|
||||
// coincides with the center of the target.
|
||||
this.snapToTarget(target);
|
||||
|
||||
// Target was found.
|
||||
// Target was found.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Target was not found.
|
||||
// Target was not found.
|
||||
return false;
|
||||
},
|
||||
|
||||
@@ -266,16 +266,16 @@
|
||||
}
|
||||
},
|
||||
|
||||
// Go through all of the draggables subtract 1 from the z-index
|
||||
// of all whose z-index is higher than the old z-index of the
|
||||
// current element. After, set the z-index of the current
|
||||
// element to 1 + N (where N is the number of draggables - i.e.
|
||||
// the highest z-index possible).
|
||||
//
|
||||
// This will make sure that after releasing a draggable, it
|
||||
// will be on top of all of the other draggables. Also, the
|
||||
// ordering of the visibility (z-index) of the other draggables
|
||||
// will not change.
|
||||
// Go through all of the draggables subtract 1 from the z-index
|
||||
// of all whose z-index is higher than the old z-index of the
|
||||
// current element. After, set the z-index of the current
|
||||
// element to 1 + N (where N is the number of draggables - i.e.
|
||||
// the highest z-index possible).
|
||||
//
|
||||
// This will make sure that after releasing a draggable, it
|
||||
// will be on top of all of the other draggables. Also, the
|
||||
// ordering of the visibility (z-index) of the other draggables
|
||||
// will not change.
|
||||
correctZIndexes: function() {
|
||||
var c1, highestZIndex;
|
||||
|
||||
@@ -285,9 +285,9 @@
|
||||
if (this.onTarget.draggableList.length > 0) {
|
||||
for (c1 = 0; c1 < this.onTarget.draggableList.length; c1 += 1) {
|
||||
if (
|
||||
(this.onTarget.draggableList[c1].zIndex > highestZIndex) &&
|
||||
(this.onTarget.draggableList[c1].zIndex > highestZIndex) &&
|
||||
(this.onTarget.draggableList[c1].zIndex !== 1000)
|
||||
) {
|
||||
) {
|
||||
highestZIndex = this.onTarget.draggableList[c1].zIndex;
|
||||
}
|
||||
}
|
||||
@@ -298,9 +298,9 @@
|
||||
for (c1 = 0; c1 < this.state.draggables.length; c1++) {
|
||||
if (this.inContainer === false) {
|
||||
if (
|
||||
(this.state.draggables[c1].zIndex > highestZIndex) &&
|
||||
(this.state.draggables[c1].zIndex > highestZIndex) &&
|
||||
(this.state.draggables[c1].zIndex !== 1000)
|
||||
) {
|
||||
) {
|
||||
highestZIndex = this.state.draggables[c1].zIndex;
|
||||
}
|
||||
}
|
||||
@@ -319,9 +319,9 @@
|
||||
}
|
||||
},
|
||||
|
||||
// If a draggable was released in a wrong positione, we will
|
||||
// move it back to the slider, placing it in the same position
|
||||
// that it was dragged out of.
|
||||
// If a draggable was released in a wrong positione, we will
|
||||
// move it back to the slider, placing it in the same position
|
||||
// that it was dragged out of.
|
||||
moveBackToSlider: function() {
|
||||
var c1;
|
||||
|
||||
@@ -364,9 +364,9 @@
|
||||
height: this.iconHeightSmall,
|
||||
left: 50 - this.iconWidthSmall * 0.5,
|
||||
|
||||
// Before:
|
||||
// 'top': ((this.labelEl !== null) ? (100 - this.iconHeightSmall - 25) * 0.5 : 50 - this.iconHeightSmall * 0.5)
|
||||
// After:
|
||||
// Before:
|
||||
// 'top': ((this.labelEl !== null) ? (100 - this.iconHeightSmall - 25) * 0.5 : 50 - this.iconHeightSmall * 0.5)
|
||||
// After:
|
||||
top: ((this.labelEl !== null) ? 37.5 : 50.0) - 0.5 * this.iconHeightSmall
|
||||
});
|
||||
this.iconEl.appendTo(this.containerEl);
|
||||
@@ -381,9 +381,9 @@
|
||||
'z-index': this.zIndex,
|
||||
left: 50 - this.labelWidth * 0.5,
|
||||
|
||||
// Before:
|
||||
// 'top': (100 - this.iconHeightSmall - 25) * 0.5 + this.iconHeightSmall + 5
|
||||
// After:
|
||||
// Before:
|
||||
// 'top': (100 - this.iconHeightSmall - 25) * 0.5 + this.iconHeightSmall + 5
|
||||
// After:
|
||||
top: 42.5 + 0.5 * this.iconHeightSmall
|
||||
});
|
||||
this.labelEl.appendTo(this.containerEl);
|
||||
|
||||
@@ -17,14 +17,14 @@
|
||||
function makeDraggableCopy(callbackFunc) {
|
||||
var draggableObj, property;
|
||||
|
||||
// Make a full proper copy of the draggable object, with some modifications.
|
||||
// Make a full proper copy of the draggable object, with some modifications.
|
||||
draggableObj = {};
|
||||
for (property in this) {
|
||||
if (this.hasOwnProperty(property) === true) {
|
||||
draggableObj[property] = this[property];
|
||||
}
|
||||
}
|
||||
// The modifications to the draggable copy.
|
||||
// The modifications to the draggable copy.
|
||||
draggableObj.isOriginal = false; // This new draggable is a copy.
|
||||
draggableObj.uniqueId = draggableObj.state.getUniqueId(); // Is newly set.
|
||||
draggableObj.stateDraggablesIndex = null; // Will be set.
|
||||
@@ -34,7 +34,7 @@
|
||||
draggableObj.labelEl = null; // Will be created.
|
||||
draggableObj.targetField = []; // Will be populated.
|
||||
|
||||
// Create DOM elements and attach events.
|
||||
// Create DOM elements and attach events.
|
||||
if (draggableObj.originalConfigObj.icon.length > 0) {
|
||||
draggableObj.iconEl = $('<div></div>');
|
||||
draggableObj.iconImgEl = $('<img />');
|
||||
@@ -63,7 +63,7 @@
|
||||
),
|
||||
draggableObj.originalConfigObj.label,
|
||||
HtmlUtils.HTML('</div>')
|
||||
).toString());
|
||||
).toString());
|
||||
draggableObj.labelEl.css({
|
||||
left: 50 - draggableObj.labelWidth * 0.5,
|
||||
top: 5 + draggableObj.iconHeightSmall + 5
|
||||
@@ -88,7 +88,7 @@
|
||||
HtmlUtils.HTML('<div style=" position: absolute; color: black; font-size: 0.95em; " >'),
|
||||
draggableObj.originalConfigObj.label,
|
||||
HtmlUtils.HTML('</div>')
|
||||
).toString());
|
||||
).toString());
|
||||
draggableObj.iconEl.css({
|
||||
left: 50 - draggableObj.iconWidthSmall * 0.5,
|
||||
top: 50 - draggableObj.iconHeightSmall * 0.5
|
||||
@@ -164,7 +164,7 @@
|
||||
HtmlUtils.HTML('<div style=" width: 100px; height: 100px; display: inline-block; overflow: hidden; '),
|
||||
HtmlUtils.HTML('border-left: 1px solid #CCC; border-right: 1px solid #CCC; text-align: center; '),
|
||||
HtmlUtils.HTML('position: relative; cursor: move; " role="listitem"></div>')
|
||||
).toString());
|
||||
).toString());
|
||||
|
||||
draggableObj.containerEl.appendTo(state.sliderEl);
|
||||
|
||||
@@ -196,9 +196,9 @@
|
||||
height: draggableObj.iconHeightSmall,
|
||||
left: 50 - draggableObj.iconWidthSmall * 0.5,
|
||||
|
||||
// Before:
|
||||
// 'top': ((obj.label.length > 0) ? (100 - draggableObj.iconHeightSmall - 25) * 0.5 : 50 - draggableObj.iconHeightSmall * 0.5)
|
||||
// After:
|
||||
// Before:
|
||||
// 'top': ((obj.label.length > 0) ? (100 - draggableObj.iconHeightSmall - 25) * 0.5 : 50 - draggableObj.iconHeightSmall * 0.5)
|
||||
// After:
|
||||
top: ((obj.label.length > 0) ? 37.5 : 50.0) - 0.5 * draggableObj.iconHeightSmall
|
||||
});
|
||||
draggableObj.iconImgEl.css({
|
||||
@@ -218,16 +218,16 @@
|
||||
),
|
||||
obj.label,
|
||||
HtmlUtils.HTML('</div>')
|
||||
).toString());
|
||||
).toString());
|
||||
|
||||
draggableObj.labelEl.appendTo(draggableObj.containerEl);
|
||||
draggableObj.labelWidth = draggableObj.labelEl.width();
|
||||
draggableObj.labelEl.css({
|
||||
left: 50 - draggableObj.labelWidth * 0.5,
|
||||
|
||||
// Before:
|
||||
// 'top': (100 - this.iconHeightSmall - 25) * 0.5 + this.iconHeightSmall + 5
|
||||
// After:
|
||||
// Before:
|
||||
// 'top': (100 - this.iconHeightSmall - 25) * 0.5 + this.iconHeightSmall + 5
|
||||
// After:
|
||||
top: 42.5 + 0.5 * draggableObj.iconHeightSmall
|
||||
});
|
||||
|
||||
@@ -254,7 +254,7 @@
|
||||
HtmlUtils.HTML('tabindex="0" aria-grabbed="false" role="listitem">'),
|
||||
obj.label,
|
||||
HtmlUtils.HTML('</div>')
|
||||
).toString());
|
||||
).toString());
|
||||
|
||||
draggableObj.iconEl.appendTo(draggableObj.containerEl);
|
||||
|
||||
|
||||
@@ -5,101 +5,101 @@
|
||||
'js/capa/drag_and_drop/base_image', 'js/capa/drag_and_drop/scroller',
|
||||
'js/capa/drag_and_drop/draggables', 'js/capa/drag_and_drop/targets',
|
||||
'js/capa/drag_and_drop/update_input'],
|
||||
function(State, configParser, Container, BaseImage, Scroller, Draggables, Targets, updateInput) {
|
||||
return Main;
|
||||
function(State, configParser, Container, BaseImage, Scroller, Draggables, Targets, updateInput) {
|
||||
return Main;
|
||||
|
||||
function Main() {
|
||||
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/every
|
||||
//
|
||||
// Array.prototype.every is a recent addition to the ECMA-262 standard; as such it may not be present in
|
||||
// other implementations of the standard.
|
||||
if (!Array.prototype.every) {
|
||||
Array.prototype.every = function(fun /* , thisp */) {
|
||||
var thisp, t, len, i;
|
||||
function Main() {
|
||||
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/every
|
||||
//
|
||||
// Array.prototype.every is a recent addition to the ECMA-262 standard; as such it may not be present in
|
||||
// other implementations of the standard.
|
||||
if (!Array.prototype.every) {
|
||||
Array.prototype.every = function(fun /* , thisp */) {
|
||||
var thisp, t, len, i;
|
||||
|
||||
if (this == null) {
|
||||
throw new TypeError();
|
||||
}
|
||||
|
||||
t = Object(this);
|
||||
len = t.length >>> 0;
|
||||
if (typeof fun !== 'function') {
|
||||
throw new TypeError();
|
||||
}
|
||||
|
||||
thisp = arguments[1];
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
if (i in t && !fun.call(thisp, t[i], i, t)) {
|
||||
return false;
|
||||
if (this == null) {
|
||||
throw new TypeError();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
t = Object(this);
|
||||
len = t.length >>> 0;
|
||||
if (typeof fun !== 'function') {
|
||||
throw new TypeError();
|
||||
}
|
||||
|
||||
thisp = arguments[1];
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
if (i in t && !fun.call(thisp, t[i], i, t)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
$('.drag_and_drop_problem_div').each(processProblem);
|
||||
}
|
||||
|
||||
$('.drag_and_drop_problem_div').each(processProblem);
|
||||
}
|
||||
// $(value) - get the element of the entire problem
|
||||
function processProblem(index, value) {
|
||||
var problemId, config, state;
|
||||
|
||||
// $(value) - get the element of the entire problem
|
||||
function processProblem(index, value) {
|
||||
var problemId, config, state;
|
||||
if ($(value).attr('data-problem-processed') === 'true') {
|
||||
// This problem was already processed by us before, so we will
|
||||
// skip it.
|
||||
|
||||
if ($(value).attr('data-problem-processed') === 'true') {
|
||||
// This problem was already processed by us before, so we will
|
||||
// skip it.
|
||||
return;
|
||||
}
|
||||
$(value).attr('data-problem-processed', 'true');
|
||||
|
||||
return;
|
||||
}
|
||||
$(value).attr('data-problem-processed', 'true');
|
||||
|
||||
problemId = $(value).attr('data-plain-id');
|
||||
if (typeof problemId !== 'string') {
|
||||
console.log('ERROR: Could not find the ID of the problem DOM element.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
config = JSON.parse($('#drag_and_drop_json_' + problemId).html());
|
||||
} catch (err) {
|
||||
console.log('ERROR: Could not parse the JSON configuration options.');
|
||||
console.log('Error message: "' + err.message + '".');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
state = State(problemId);
|
||||
|
||||
if (configParser(state, config) !== true) {
|
||||
console.log('ERROR: Could not make sense of the JSON configuration options.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Container(state);
|
||||
BaseImage(state);
|
||||
|
||||
(function addContent() {
|
||||
if (state.baseImageLoaded !== true) {
|
||||
setTimeout(addContent, 50);
|
||||
problemId = $(value).attr('data-plain-id');
|
||||
if (typeof problemId !== 'string') {
|
||||
console.log('ERROR: Could not find the ID of the problem DOM element.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Targets.initializeBaseTargets(state);
|
||||
Scroller(state);
|
||||
Draggables.init(state);
|
||||
try {
|
||||
config = JSON.parse($('#drag_and_drop_json_' + problemId).html());
|
||||
} catch (err) {
|
||||
console.log('ERROR: Could not parse the JSON configuration options.');
|
||||
console.log('Error message: "' + err.message + '".');
|
||||
|
||||
state.updateArrowOpacity();
|
||||
|
||||
// Update the input element, checking first that it is not filled with
|
||||
// an answer from the server.
|
||||
if (updateInput.check(state) === false) {
|
||||
updateInput.update(state);
|
||||
return;
|
||||
}
|
||||
}());
|
||||
}
|
||||
}); // End-of: define(
|
||||
|
||||
state = State(problemId);
|
||||
|
||||
if (configParser(state, config) !== true) {
|
||||
console.log('ERROR: Could not make sense of the JSON configuration options.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Container(state);
|
||||
BaseImage(state);
|
||||
|
||||
(function addContent() {
|
||||
if (state.baseImageLoaded !== true) {
|
||||
setTimeout(addContent, 50);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Targets.initializeBaseTargets(state);
|
||||
Scroller(state);
|
||||
Draggables.init(state);
|
||||
|
||||
state.updateArrowOpacity();
|
||||
|
||||
// Update the input element, checking first that it is not filled with
|
||||
// an answer from the server.
|
||||
if (updateInput.check(state) === false) {
|
||||
updateInput.update(state);
|
||||
}
|
||||
}());
|
||||
}
|
||||
}); // End-of: define(
|
||||
}(RequireJS.requirejs, RequireJS.require, RequireJS.define)); // End-of: (function (requirejs, require, define) {
|
||||
|
||||
@@ -10,47 +10,47 @@
|
||||
).toString());
|
||||
|
||||
$moveLeftEl = $(HtmlUtils.joinHtml(
|
||||
HtmlUtils.HTML('<div style=" width: 40px; height: 102px; display: inline; float: left; " >'),
|
||||
HtmlUtils.HTML('<div style=" width: 40px; height: 102px; display: inline; float: left; " >'),
|
||||
HtmlUtils.HTML('<div style=" width: 38px; height: 100px; border: 1px solid #CCC; '),
|
||||
HtmlUtils.HTML('background-color: #EEE; '),
|
||||
HtmlUtils.HTML('background-image: -webkit-linear-gradient(top, #EEE, #DDD); '),
|
||||
HtmlUtils.HTML('background-image: -moz-linear-gradient(top, #EEE, #DDD); '),
|
||||
HtmlUtils.HTML('background-image: -ms-linear-gradient(top, #EEE, #DDD); '),
|
||||
HtmlUtils.HTML('background-image: -o-linear-gradient(top, #EEE, #DDD); '),
|
||||
HtmlUtils.HTML('background-image: linear-gradient(top, #EEE, #DDD); '),
|
||||
HtmlUtils.HTML('-webkit-box-shadow: 0 1px 0 rgba(255, 255, 255, 0.7) inset; '),
|
||||
HtmlUtils.HTML('box-shadow: 0 1px 0 rgba(255, 255, 255, 0.7) inset; '),
|
||||
// eslint-disable-next-line no-undef
|
||||
HtmlUtils.HTML("background-image: url('"), baseUrl,
|
||||
HtmlUtils.HTML("images/arrow-left.png'); "),
|
||||
HtmlUtils.HTML('background-position: center center; '),
|
||||
HtmlUtils.HTML('background-repeat: no-repeat; " >'),
|
||||
HtmlUtils.HTML('background-color: #EEE; '),
|
||||
HtmlUtils.HTML('background-image: -webkit-linear-gradient(top, #EEE, #DDD); '),
|
||||
HtmlUtils.HTML('background-image: -moz-linear-gradient(top, #EEE, #DDD); '),
|
||||
HtmlUtils.HTML('background-image: -ms-linear-gradient(top, #EEE, #DDD); '),
|
||||
HtmlUtils.HTML('background-image: -o-linear-gradient(top, #EEE, #DDD); '),
|
||||
HtmlUtils.HTML('background-image: linear-gradient(top, #EEE, #DDD); '),
|
||||
HtmlUtils.HTML('-webkit-box-shadow: 0 1px 0 rgba(255, 255, 255, 0.7) inset; '),
|
||||
HtmlUtils.HTML('box-shadow: 0 1px 0 rgba(255, 255, 255, 0.7) inset; '),
|
||||
// eslint-disable-next-line no-undef
|
||||
HtmlUtils.HTML("background-image: url('"), baseUrl,
|
||||
HtmlUtils.HTML("images/arrow-left.png'); "),
|
||||
HtmlUtils.HTML('background-position: center center; '),
|
||||
HtmlUtils.HTML('background-repeat: no-repeat; " >'),
|
||||
HtmlUtils.HTML('</div>'),
|
||||
HtmlUtils.HTML('</div>')
|
||||
).toString());
|
||||
HtmlUtils.HTML('</div>')
|
||||
).toString());
|
||||
$moveLeftEl.appendTo($parentEl);
|
||||
|
||||
// The below is necessary to prevent the browser thinking that we want
|
||||
// to perform a drag operation, or a highlight operation. If we don't
|
||||
// do this, the browser will then highlight with a gray shade the
|
||||
// element.
|
||||
// The below is necessary to prevent the browser thinking that we want
|
||||
// to perform a drag operation, or a highlight operation. If we don't
|
||||
// do this, the browser will then highlight with a gray shade the
|
||||
// element.
|
||||
$moveLeftEl.mousemove(function(event) { event.preventDefault(); });
|
||||
$moveLeftEl.mousedown(function(event) { event.preventDefault(); });
|
||||
|
||||
// This event will be responsible for moving the scroller left.
|
||||
// Hidden draggables will be shown.
|
||||
// This event will be responsible for moving the scroller left.
|
||||
// Hidden draggables will be shown.
|
||||
$moveLeftEl.mouseup(function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
// When there are no more hidden draggables, prevent from
|
||||
// scrolling infinitely.
|
||||
// When there are no more hidden draggables, prevent from
|
||||
// scrolling infinitely.
|
||||
if (showElLeftMargin > -102) {
|
||||
return;
|
||||
}
|
||||
|
||||
showElLeftMargin += 102;
|
||||
|
||||
// We scroll by changing the 'margin-left' CSS property smoothly.
|
||||
// We scroll by changing the 'margin-left' CSS property smoothly.
|
||||
state.sliderEl.animate({
|
||||
'margin-left': showElLeftMargin + 'px'
|
||||
}, 100, function() {
|
||||
@@ -65,10 +65,10 @@
|
||||
|
||||
showElLeftMargin = 0;
|
||||
|
||||
// Element where the draggables will be contained. It is very long
|
||||
// so that any SANE number of draggables will fit in a single row. It
|
||||
// will be contained in a parent element whose 'overflow' CSS value
|
||||
// will be hidden, preventing the long row from fully being visible.
|
||||
// Element where the draggables will be contained. It is very long
|
||||
// so that any SANE number of draggables will fit in a single row. It
|
||||
// will be contained in a parent element whose 'overflow' CSS value
|
||||
// will be hidden, preventing the long row from fully being visible.
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
state.sliderEl = $(HtmlUtils.joinHtml(
|
||||
HtmlUtils.HTML('<div style=" width: 20000px; height: 100px; border-top: 1px solid #CCC; '),
|
||||
@@ -81,47 +81,47 @@
|
||||
});
|
||||
|
||||
$moveRightEl = $(HtmlUtils.joinHtml(
|
||||
HtmlUtils.HTML('<div style=" width: 40px; height: 102px; display: inline; float: left; " >'),
|
||||
HtmlUtils.HTML('<div style=" width: 40px; height: 102px; display: inline; float: left; " >'),
|
||||
HtmlUtils.HTML('<div style=" width: 38px; height: 100px; border: 1px solid #CCC; '),
|
||||
HtmlUtils.HTML('background-color: #EEE; '),
|
||||
HtmlUtils.HTML('background-image: -webkit-linear-gradient(top, #EEE, #DDD); '),
|
||||
HtmlUtils.HTML('background-image: -moz-linear-gradient(top, #EEE, #DDD); '),
|
||||
HtmlUtils.HTML('background-image: -ms-linear-gradient(top, #EEE, #DDD); '),
|
||||
HtmlUtils.HTML('background-image: -o-linear-gradient(top, #EEE, #DDD); '),
|
||||
HtmlUtils.HTML('background-image: linear-gradient(top, #EEE, #DDD); '),
|
||||
HtmlUtils.HTML('-webkit-box-shadow: 0 1px 0 rgba(255, 255, 255, 0.7) inset; '),
|
||||
HtmlUtils.HTML('box-shadow: 0 1px 0 rgba(255, 255, 255, 0.7) inset; '),
|
||||
// eslint-disable-next-line no-undef
|
||||
HtmlUtils.HTML("background-image: url('"), baseUrl,
|
||||
HtmlUtils.HTML("images/arrow-right.png'); "),
|
||||
HtmlUtils.HTML('background-position: center center; '),
|
||||
HtmlUtils.HTML('background-repeat: no-repeat; " >'),
|
||||
HtmlUtils.HTML('background-color: #EEE; '),
|
||||
HtmlUtils.HTML('background-image: -webkit-linear-gradient(top, #EEE, #DDD); '),
|
||||
HtmlUtils.HTML('background-image: -moz-linear-gradient(top, #EEE, #DDD); '),
|
||||
HtmlUtils.HTML('background-image: -ms-linear-gradient(top, #EEE, #DDD); '),
|
||||
HtmlUtils.HTML('background-image: -o-linear-gradient(top, #EEE, #DDD); '),
|
||||
HtmlUtils.HTML('background-image: linear-gradient(top, #EEE, #DDD); '),
|
||||
HtmlUtils.HTML('-webkit-box-shadow: 0 1px 0 rgba(255, 255, 255, 0.7) inset; '),
|
||||
HtmlUtils.HTML('box-shadow: 0 1px 0 rgba(255, 255, 255, 0.7) inset; '),
|
||||
// eslint-disable-next-line no-undef
|
||||
HtmlUtils.HTML("background-image: url('"), baseUrl,
|
||||
HtmlUtils.HTML("images/arrow-right.png'); "),
|
||||
HtmlUtils.HTML('background-position: center center; '),
|
||||
HtmlUtils.HTML('background-repeat: no-repeat; " >'),
|
||||
HtmlUtils.HTML('</div>'),
|
||||
HtmlUtils.HTML('</div>')
|
||||
).toString());
|
||||
HtmlUtils.HTML('</div>')
|
||||
).toString());
|
||||
$moveRightEl.appendTo($parentEl);
|
||||
|
||||
// The below is necessary to prevent the browser thinking that we want
|
||||
// to perform a drag operation, or a highlight operation. If we don't
|
||||
// do this, the browser will then highlight with a gray shade the
|
||||
// element.
|
||||
// The below is necessary to prevent the browser thinking that we want
|
||||
// to perform a drag operation, or a highlight operation. If we don't
|
||||
// do this, the browser will then highlight with a gray shade the
|
||||
// element.
|
||||
$moveRightEl.mousemove(function(event) { event.preventDefault(); });
|
||||
$moveRightEl.mousedown(function(event) { event.preventDefault(); });
|
||||
|
||||
// This event will be responsible for moving the scroller right.
|
||||
// Hidden draggables will be shown.
|
||||
// This event will be responsible for moving the scroller right.
|
||||
// Hidden draggables will be shown.
|
||||
$moveRightEl.mouseup(function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
// When there are no more hidden draggables, prevent from
|
||||
// scrolling infinitely.
|
||||
// When there are no more hidden draggables, prevent from
|
||||
// scrolling infinitely.
|
||||
if (showElLeftMargin < -102 * (state.numDraggablesInSlider - 6)) {
|
||||
return;
|
||||
}
|
||||
|
||||
showElLeftMargin -= 102;
|
||||
|
||||
// We scroll by changing the 'margin-left' CSS property smoothly.
|
||||
// We scroll by changing the 'margin-left' CSS property smoothly.
|
||||
state.sliderEl.animate({
|
||||
'margin-left': showElLeftMargin + 'px'
|
||||
}, 100, function() {
|
||||
@@ -131,16 +131,16 @@
|
||||
|
||||
$parentEl.appendTo(state.containerEl);
|
||||
|
||||
// Make the function available throughout the application. We need to
|
||||
// call it in several places:
|
||||
//
|
||||
// 1.) When initially reading answer from server, if draggables will be
|
||||
// positioned on the base image, the scroller's right and left arrows
|
||||
// opacity must be updated.
|
||||
//
|
||||
// 2.) When creating draggable elements, the scroller's right and left
|
||||
// arrows opacity must be updated according to the number of
|
||||
// draggables.
|
||||
// Make the function available throughout the application. We need to
|
||||
// call it in several places:
|
||||
//
|
||||
// 1.) When initially reading answer from server, if draggables will be
|
||||
// positioned on the base image, the scroller's right and left arrows
|
||||
// opacity must be updated.
|
||||
//
|
||||
// 2.) When creating draggable elements, the scroller's right and left
|
||||
// arrows opacity must be updated according to the number of
|
||||
// draggables.
|
||||
state.updateArrowOpacity = updateArrowOpacity;
|
||||
|
||||
return;
|
||||
|
||||
@@ -60,34 +60,34 @@
|
||||
function documentMouseMove(state, event) {
|
||||
if (state.currentMovingDraggable !== null) {
|
||||
state.currentMovingDraggable.iconEl.css(
|
||||
'left',
|
||||
event.pageX -
|
||||
'left',
|
||||
event.pageX -
|
||||
state.baseImageEl.offset().left -
|
||||
state.currentMovingDraggable.iconWidth * 0.5
|
||||
- state.currentMovingDraggable.iconElLeftOffset
|
||||
);
|
||||
);
|
||||
state.currentMovingDraggable.iconEl.css(
|
||||
'top',
|
||||
event.pageY -
|
||||
'top',
|
||||
event.pageY -
|
||||
state.baseImageEl.offset().top -
|
||||
state.currentMovingDraggable.iconHeight * 0.5
|
||||
);
|
||||
);
|
||||
|
||||
if (state.currentMovingDraggable.labelEl !== null) {
|
||||
state.currentMovingDraggable.labelEl.css(
|
||||
'left',
|
||||
event.pageX -
|
||||
'left',
|
||||
event.pageX -
|
||||
state.baseImageEl.offset().left -
|
||||
state.currentMovingDraggable.labelWidth * 0.5
|
||||
- 9 // Account for padding, border.
|
||||
);
|
||||
);
|
||||
state.currentMovingDraggable.labelEl.css(
|
||||
'top',
|
||||
event.pageY -
|
||||
'top',
|
||||
event.pageY -
|
||||
state.baseImageEl.offset().top +
|
||||
state.currentMovingDraggable.iconHeight * 0.5 +
|
||||
5
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
borderCss,
|
||||
HtmlUtils.HTML('"aria-dropeffect=""></div>')
|
||||
).toString()
|
||||
);
|
||||
);
|
||||
if (fromTargetField === true) {
|
||||
$targetEl.appendTo(draggableObj.iconEl);
|
||||
} else {
|
||||
@@ -178,8 +178,8 @@
|
||||
|
||||
this.draggableList.splice(draggable.onTargetIndex, 1);
|
||||
|
||||
// An item from the array was removed. We need to updated all indexes accordingly.
|
||||
// Shift all indexes down by one if they are higher than the index of the removed item.
|
||||
// An item from the array was removed. We need to updated all indexes accordingly.
|
||||
// Shift all indexes down by one if they are higher than the index of the removed item.
|
||||
c1 = 0;
|
||||
while (c1 < this.draggableList.length) {
|
||||
if (this.draggableList[c1].onTargetIndex > draggable.onTargetIndex) {
|
||||
@@ -210,7 +210,7 @@
|
||||
this.updateNumTextEl();
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* function cycleDraggableOrder
|
||||
*
|
||||
* Parameters:
|
||||
|
||||
@@ -64,8 +64,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Check if input has an answer from server. If yes, then position
|
||||
// all draggables according to answer.
|
||||
// Check if input has an answer from server. If yes, then position
|
||||
// all draggables according to answer.
|
||||
function check(state) {
|
||||
var inputElVal;
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
|
||||
baseDraggableId = Object.keys(chain)[0];
|
||||
|
||||
// This is a hack. For now we will work with depths 1 and 3.
|
||||
// This is a hack. For now we will work with depths 1 and 3.
|
||||
if (depth === 1) {
|
||||
baseTargetId = chain[baseDraggableId];
|
||||
|
||||
@@ -188,10 +188,10 @@
|
||||
if ((draggable = getById(state, 'draggables', draggableId)) === null) {
|
||||
if (reportError !== false) {
|
||||
console.log(
|
||||
'ERROR: In answer there exists a ' +
|
||||
'ERROR: In answer there exists a ' +
|
||||
'draggable ID "' + draggableId + '". No ' +
|
||||
'draggable with this ID could be found.'
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -200,10 +200,10 @@
|
||||
if ((target = getById(state, 'targets', targetId)) === null) {
|
||||
if (reportError !== false) {
|
||||
console.log(
|
||||
'ERROR: In answer there exists a target ' +
|
||||
'ERROR: In answer there exists a target ' +
|
||||
'ID "' + targetId + '". No target with this ' +
|
||||
'ID could be found.'
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -226,10 +226,10 @@
|
||||
|
||||
if ((draggable = getById(state, 'draggables', draggableId)) === null) {
|
||||
console.log(
|
||||
'ERROR: In answer there exists a ' +
|
||||
'ERROR: In answer there exists a ' +
|
||||
'draggable ID "' + draggableId + '". No ' +
|
||||
'draggable with this ID could be found.'
|
||||
);
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
@@ -277,7 +277,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// For now we support only one case.
|
||||
// For now we support only one case.
|
||||
if ((minDepth < 1) || (maxDepth > 3)) {
|
||||
return;
|
||||
}
|
||||
@@ -313,21 +313,21 @@
|
||||
if (type === 'draggables') {
|
||||
if ((targetId !== undefined) && (inContainer === false) && (baseDraggableId !== undefined) && (baseTargetId !== undefined)) {
|
||||
if (
|
||||
(state[type][c1].id === id) &&
|
||||
(state[type][c1].id === id) &&
|
||||
(state[type][c1].inContainer === false) &&
|
||||
(state[type][c1].onTarget.id === targetId) &&
|
||||
(state[type][c1].onTarget.type === 'on_drag') &&
|
||||
(state[type][c1].onTarget.draggableObj.id === baseDraggableId) &&
|
||||
(state[type][c1].onTarget.draggableObj.onTarget.id === baseTargetId)
|
||||
) {
|
||||
) {
|
||||
return state[type][c1];
|
||||
}
|
||||
} else if ((targetId !== undefined) && (inContainer === false)) {
|
||||
if (
|
||||
(state[type][c1].id === id) &&
|
||||
(state[type][c1].id === id) &&
|
||||
(state[type][c1].inContainer === false) &&
|
||||
(state[type][c1].onTarget.id === targetId)
|
||||
) {
|
||||
) {
|
||||
return state[type][c1];
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -21,7 +21,7 @@ $(function() {
|
||||
var editingCircuit = null;
|
||||
// Notice we use live, because new circuits can be inserted
|
||||
$('.schematic_open').live('click', function() {
|
||||
// Find the new editingCircuit. Transfer its contents to the editorCircuit
|
||||
// Find the new editingCircuit. Transfer its contents to the editorCircuit
|
||||
editingCircuit = $(this).children('input.schematic').get(0);
|
||||
|
||||
editingCircuit.schematic.update_value();
|
||||
@@ -35,7 +35,7 @@ $(function() {
|
||||
});
|
||||
|
||||
$('#circuit_save_btn').click(function() {
|
||||
// Take the circuit from the editor and put it back into editingCircuit
|
||||
// Take the circuit from the editor and put it back into editingCircuit
|
||||
editorCircuit.schematic.update_value();
|
||||
var saving_circuit = $(editorCircuit).val();
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ describe('escapeSelector', function() {
|
||||
// escaped when embedding/searching xblock IDs using css selectors, bad things happen.
|
||||
expect(escapeSelector('course-v1:edX+DemoX+Demo_Course')).toEqual('course-v1\\:edX\\+DemoX\\+Demo_Course');
|
||||
expect(escapeSelector('block-v1:edX+DemoX+Demo_Course+type@sequential+block')).toEqual(
|
||||
'block-v1\\:edX\\+DemoX\\+Demo_Course\\+type\\@sequential\\+block'
|
||||
'block-v1\\:edX\\+DemoX\\+Demo_Course\\+type\\@sequential\\+block'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -292,7 +292,7 @@ var Channel = (function() {
|
||||
var m = JSON.parse(e.data);
|
||||
if (typeof m !== 'object' || m === null) throw 'malformed';
|
||||
} catch (e) {
|
||||
// just ignore any posted messages that do not consist of valid JSON
|
||||
// just ignore any posted messages that do not consist of valid JSON
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -501,7 +501,7 @@ var Channel = (function() {
|
||||
var setTransactionTimeout = function(transId, timeout, method) {
|
||||
return window.setTimeout(function() {
|
||||
if (outTbl[transId]) {
|
||||
// XXX: what if client code raises an exception here?
|
||||
// XXX: what if client code raises an exception here?
|
||||
var msg = 'timeout (' + timeout + "ms) exceeded on method '" + method + "'";
|
||||
(1, outTbl[transId].error)('timeout_error', msg);
|
||||
delete outTbl[transId];
|
||||
@@ -738,10 +738,10 @@ var Channel = (function() {
|
||||
if (callbackNames.length) msg.callbacks = callbackNames;
|
||||
|
||||
if (m.timeout)
|
||||
// XXX: This function returns a timeout ID, but we don't do anything with it.
|
||||
// We might want to keep track of it so we can cancel it using clearTimeout()
|
||||
// when the transaction completes.
|
||||
{ setTransactionTimeout(s_curTranId, m.timeout, scopeMethod(m.method)); }
|
||||
// XXX: This function returns a timeout ID, but we don't do anything with it.
|
||||
// We might want to keep track of it so we can cancel it using clearTimeout()
|
||||
// when the transaction completes.
|
||||
{ setTransactionTimeout(s_curTranId, m.timeout, scopeMethod(m.method)); }
|
||||
|
||||
// insert into the transaction table
|
||||
outTbl[s_curTranId] = {callbacks: callbacks, error: m.error, success: m.success};
|
||||
|
||||
@@ -17,17 +17,17 @@ window.SymbolicMathjaxPreprocessor = function() {
|
||||
var superscriptsOn = true;
|
||||
|
||||
if (superscriptsOn) {
|
||||
// find instances of "__" and make them superscripts ("^") and tag them
|
||||
// as such. Specifcally replace instances of "__X" or "__{XYZ}" with
|
||||
// "^{CHAR$1}", marking superscripts as different from powers
|
||||
// find instances of "__" and make them superscripts ("^") and tag them
|
||||
// as such. Specifcally replace instances of "__X" or "__{XYZ}" with
|
||||
// "^{CHAR$1}", marking superscripts as different from powers
|
||||
|
||||
// a zero width space--this is an invisible character that no one would
|
||||
// use, that gets passed through MathJax and to the server
|
||||
// a zero width space--this is an invisible character that no one would
|
||||
// use, that gets passed through MathJax and to the server
|
||||
var c = '\u200b';
|
||||
eqn = eqn.replace(/__(?:([^\{])|\{([^\}]+)\})/g, '^{' + c + '$1$2}');
|
||||
|
||||
// NOTE: MathJax supports '\class{name}{mathcode}' but not for asciimath
|
||||
// input, which is too bad. This would be preferable to this char tag
|
||||
// NOTE: MathJax supports '\class{name}{mathcode}' but not for asciimath
|
||||
// input, which is too bad. This would be preferable to this char tag
|
||||
}
|
||||
|
||||
return eqn;
|
||||
|
||||
@@ -40,22 +40,22 @@ describe('Tests for accessibility_tools.js', function() {
|
||||
expect($('#submit')).toHaveAttr('tabindex', '2');
|
||||
});
|
||||
|
||||
// for some reason, toBeFocused tests don't pass with js-test-tool
|
||||
// (they do when run locally on browsers), so we're skipping them temporarily
|
||||
// for some reason, toBeFocused tests don't pass with js-test-tool
|
||||
// (they do when run locally on browsers), so we're skipping them temporarily
|
||||
xit('shifts focus to close-modal button', function() {
|
||||
expect($('#close-modal')).toBeFocused();
|
||||
});
|
||||
|
||||
// for some reason, toBeFocused tests don't pass with js-test-tool
|
||||
// (they do when run locally on browsers), so we're skipping them temporarily
|
||||
// for some reason, toBeFocused tests don't pass with js-test-tool
|
||||
// (they do when run locally on browsers), so we're skipping them temporarily
|
||||
xit('tab on last element in modal returns to the close-modal button', function() {
|
||||
$('#submit').focus();
|
||||
pressTabOnLastElt($('#close-modal'), $('#submit'));
|
||||
expect($('#close-modal')).toBeFocused();
|
||||
});
|
||||
|
||||
// for some reason, toBeFocused tests don't pass with js-test-tool
|
||||
// (they do when run locally on browsers), so we're skipping them temporarily
|
||||
// for some reason, toBeFocused tests don't pass with js-test-tool
|
||||
// (they do when run locally on browsers), so we're skipping them temporarily
|
||||
xit('shift-tab on close-modal element in modal returns to the last element in modal', function() {
|
||||
$('#close-modal').focus();
|
||||
pressShiftTabOnFirstElt($('#close-modal'), $('#submit'));
|
||||
@@ -84,8 +84,8 @@ describe('Tests for accessibility_tools.js', function() {
|
||||
expect($('#modalId')).toHaveAttr('aria-hidden', 'true');
|
||||
});
|
||||
|
||||
// for some reason, toBeFocused tests don't pass with js-test-tool
|
||||
// (they do when run locally on browsers), so we're skipping them temporarily
|
||||
// for some reason, toBeFocused tests don't pass with js-test-tool
|
||||
// (they do when run locally on browsers), so we're skipping them temporarily
|
||||
xit('returns focus to focusedElementBeforeModal', function() {
|
||||
expect(focusedElementBeforeModal).toBeFocused();
|
||||
});
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
// supported in older browsers
|
||||
|
||||
var pointerEventsNone = function(selector, supportedStyles) {
|
||||
// Check to see if the browser supports 'pointer-events' css rule.
|
||||
// If it doesn't, use javascript to stop the link from working
|
||||
// when clicked.
|
||||
// Check to see if the browser supports 'pointer-events' css rule.
|
||||
// If it doesn't, use javascript to stop the link from working
|
||||
// when clicked.
|
||||
$(selector).click(function(event) {
|
||||
if (!('pointerEvents' in supportedStyles)) {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -2,65 +2,65 @@ import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
class ReactRendererException extends Error {
|
||||
constructor(message) {
|
||||
super(`ReactRendererException: ${message}`);
|
||||
Error.captureStackTrace(this, ReactRendererException);
|
||||
}
|
||||
constructor(message) {
|
||||
super(`ReactRendererException: ${message}`);
|
||||
Error.captureStackTrace(this, ReactRendererException);
|
||||
}
|
||||
}
|
||||
|
||||
export class ReactRenderer {
|
||||
constructor({ component, selector, componentName, props = {} }) {
|
||||
Object.assign(this, {
|
||||
component,
|
||||
selector,
|
||||
componentName,
|
||||
props,
|
||||
});
|
||||
this.handleArgumentErrors();
|
||||
this.targetElement = this.getTargetElement();
|
||||
this.renderComponent();
|
||||
}
|
||||
constructor({ component, selector, componentName, props = {} }) {
|
||||
Object.assign(this, {
|
||||
component,
|
||||
selector,
|
||||
componentName,
|
||||
props,
|
||||
});
|
||||
this.handleArgumentErrors();
|
||||
this.targetElement = this.getTargetElement();
|
||||
this.renderComponent();
|
||||
}
|
||||
|
||||
handleArgumentErrors() {
|
||||
if (this.component === null) {
|
||||
throw new ReactRendererException(
|
||||
`Component ${this.componentName} is not defined. Make sure you're ` +
|
||||
handleArgumentErrors() {
|
||||
if (this.component === null) {
|
||||
throw new ReactRendererException(
|
||||
`Component ${this.componentName} is not defined. Make sure you're ` +
|
||||
`using a non-default export statement for the ${this.componentName} ` +
|
||||
`class, that ${this.componentName} has an entry point defined ` +
|
||||
'within the \'entry\' section of webpack.common.config.js, and that the ' +
|
||||
'entry point is pointing at the correct file path.',
|
||||
);
|
||||
}
|
||||
if (!(this.props instanceof Object && this.props.constructor === Object)) {
|
||||
let propsType = typeof this.props;
|
||||
if (Array.isArray(this.props)) {
|
||||
propsType = 'array';
|
||||
} else if (this.props === null) {
|
||||
propsType = 'null';
|
||||
}
|
||||
throw new ReactRendererException(
|
||||
`Invalid props passed to component ${this.componentName}. Expected ` +
|
||||
);
|
||||
}
|
||||
if (!(this.props instanceof Object && this.props.constructor === Object)) {
|
||||
let propsType = typeof this.props;
|
||||
if (Array.isArray(this.props)) {
|
||||
propsType = 'array';
|
||||
} else if (this.props === null) {
|
||||
propsType = 'null';
|
||||
}
|
||||
throw new ReactRendererException(
|
||||
`Invalid props passed to component ${this.componentName}. Expected ` +
|
||||
`an object, but received a ${propsType}.`,
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getTargetElement() {
|
||||
const elementList = document.querySelectorAll(this.selector);
|
||||
if (elementList.length !== 1) {
|
||||
throw new ReactRendererException(
|
||||
`Expected 1 element match for selector "${this.selector}" ` +
|
||||
getTargetElement() {
|
||||
const elementList = document.querySelectorAll(this.selector);
|
||||
if (elementList.length !== 1) {
|
||||
throw new ReactRendererException(
|
||||
`Expected 1 element match for selector "${this.selector}" ` +
|
||||
`but received ${elementList.length} matches.`,
|
||||
);
|
||||
} else {
|
||||
return elementList[0];
|
||||
);
|
||||
} else {
|
||||
return elementList[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
renderComponent() {
|
||||
ReactDOM.render(
|
||||
React.createElement(this.component, this.props, null),
|
||||
this.targetElement,
|
||||
);
|
||||
}
|
||||
renderComponent() {
|
||||
ReactDOM.render(
|
||||
React.createElement(this.component, this.props, null),
|
||||
this.targetElement,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,10 +128,10 @@ var trapFocusForAccessibleModal = function(
|
||||
// to ensure that the correct elements are accessible.
|
||||
var focusableItems, $last;
|
||||
focusableItems = reassignTabIndexesAndAriaHidden(
|
||||
focusableElementsFilterString,
|
||||
closeButtonId,
|
||||
modalId,
|
||||
mainPageId
|
||||
focusableElementsFilterString,
|
||||
closeButtonId,
|
||||
modalId,
|
||||
mainPageId
|
||||
);
|
||||
$last = trapTabFocus(focusableItems, closeButtonId);
|
||||
trapShiftTabFocus($last, closeButtonId);
|
||||
@@ -140,17 +140,17 @@ var trapFocusForAccessibleModal = function(
|
||||
};
|
||||
|
||||
var accessible_modal = function(trigger, closeButtonId, modalId, mainPageId) {
|
||||
// Modifies a lean modal to optimize focus management.
|
||||
// "trigger" is the selector for the link element that triggers the modal.
|
||||
// "closeButtonId" is the selector for the button that closes out the modal.
|
||||
// "modalId" is the selector for the modal being managed
|
||||
// "mainPageId" is the selector for the main part of the page
|
||||
//
|
||||
// based on http://accessibility.oit.ncsu.edu/training/aria/modal-window/modal-window.js
|
||||
//
|
||||
// see http://accessibility.oit.ncsu.edu/blog/2013/09/13/the-incredible-accessible-modal-dialog/
|
||||
// for more information on managing modals
|
||||
//
|
||||
// Modifies a lean modal to optimize focus management.
|
||||
// "trigger" is the selector for the link element that triggers the modal.
|
||||
// "closeButtonId" is the selector for the button that closes out the modal.
|
||||
// "modalId" is the selector for the modal being managed
|
||||
// "mainPageId" is the selector for the main part of the page
|
||||
//
|
||||
// based on http://accessibility.oit.ncsu.edu/training/aria/modal-window/modal-window.js
|
||||
//
|
||||
// see http://accessibility.oit.ncsu.edu/blog/2013/09/13/the-incredible-accessible-modal-dialog/
|
||||
// for more information on managing modals
|
||||
//
|
||||
var initialFocus
|
||||
$(trigger).click(function() {
|
||||
$focusedElementBeforeModal = $(trigger);
|
||||
|
||||
@@ -9,7 +9,7 @@ var Language = (function() {
|
||||
this.listenForLanguagePreferenceChange();
|
||||
},
|
||||
|
||||
/**
|
||||
/**
|
||||
* Listener on changing language from selector.
|
||||
* Send an ajax request to save user language preferences.
|
||||
*/
|
||||
@@ -21,8 +21,8 @@ var Language = (function() {
|
||||
event.preventDefault();
|
||||
self.submitAjaxRequest(language, url, function() {
|
||||
if (is_user_authenticated) {
|
||||
// User language preference has been set successfully
|
||||
// Now submit the form in success callback.
|
||||
// User language preference has been set successfully
|
||||
// Now submit the form in success callback.
|
||||
$('#language-settings-form').submit();
|
||||
} else {
|
||||
self.refresh();
|
||||
@@ -31,7 +31,7 @@ var Language = (function() {
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
/**
|
||||
* Send an ajax request to set user language preferences.
|
||||
*/
|
||||
submitAjaxRequest: function(language, url, callback) {
|
||||
@@ -52,11 +52,11 @@ var Language = (function() {
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
/**
|
||||
* refresh the page.
|
||||
*/
|
||||
refresh: function() {
|
||||
// reloading the page so we can get the latest state of released languages from model
|
||||
// reloading the page so we can get the latest state of released languages from model
|
||||
location.reload();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user