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:
Syed Ali Abbas Zaidi
2023-03-02 16:16:50 +05:00
committed by GitHub
parent 9cdbe72b82
commit 5549db4d80
495 changed files with 74392 additions and 44320 deletions

View File

@@ -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',
},
},
};

View File

@@ -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])} &quot;
<a
href="#_"
onClick={(event) => {
event.preventDefault();
onSelectBlock(blocks.id);
}}
title={`${gettext('Select')} ${gettext(BLOCK_TYPE_NAME[blocks.type])}`}
>
{blocks.display_name}
</a>&quot;:
</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])} &quot;
<a
href="#_"
onClick={(event) => {
event.preventDefault();
onSelectBlock(blocks.id);
}}
title={`${gettext('Select')} ${gettext(BLOCK_TYPE_NAME[blocks.type])}`}
>
{blocks.display_name}
</a>&quot;:
</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,
};

View File

@@ -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();
});
});

View File

@@ -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;

View File

@@ -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',
};

View File

@@ -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,
};

View File

@@ -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,
},
);

View File

@@ -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,
});

View File

@@ -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);
};

View File

@@ -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),
);

View File

@@ -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;

View File

@@ -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
};

View File

@@ -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);

View File

@@ -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);

View File

@@ -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'];

View File

@@ -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);

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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>')
)
);

View File

@@ -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);

View File

@@ -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() {

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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
};
}
);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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));
}

View File

@@ -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();
});

View File

@@ -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!');

View File

@@ -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;
}

View File

@@ -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({

View File

@@ -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);

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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
);
);
}
}
}

View File

@@ -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:

View File

@@ -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 {

View File

@@ -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();

View File

@@ -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'
);
});
});

View File

@@ -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};

View File

@@ -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;

View File

@@ -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();
});

View File

@@ -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();

View File

@@ -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,
);
}
}

View File

@@ -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);

View File

@@ -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();
}