From a176733eb09b8e562ed908048a1eb0cf9f749ad0 Mon Sep 17 00:00:00 2001 From: Andrew Burnes Date: Thu, 9 Aug 2018 11:09:12 -0300 Subject: [PATCH 01/49] Add eslint, prettier formatting --- .eslintrc | 32 + .prettierrc | 4 + fec/fec/static/js/data-init.js | 14 +- fec/fec/static/js/draftail/Anchor.js | 26 +- fec/fec/static/js/draftail/Glossary.js | 43 +- fec/fec/static/js/draftail/SansSerif.js | 31 +- .../static/js/draftail/components/Modal.js | 11 +- fec/fec/static/js/draftail/utils.js | 4 +- fec/fec/static/js/init.js | 13 +- fec/fec/static/js/legal.js | 8 +- fec/fec/static/js/legal/.eslintrc.yml | 1 - fec/fec/static/js/legal/FilterPanel.js | 48 +- fec/fec/static/js/legal/Filters.js | 171 +- fec/fec/static/js/legal/LegalApp.js | 4 +- fec/fec/static/js/legal/LegalSearch.js | 171 +- fec/fec/static/js/legal/Pagination.js | 54 +- fec/fec/static/js/legal/SearchResults.js | 196 +- fec/fec/static/js/legal/Tags.js | 152 +- fec/fec/static/js/legal/documentTypes.js | 19 +- fec/fec/static/js/legal/filters/Checkbox.js | 15 +- .../static/js/legal/filters/CheckboxFilter.js | 12 +- .../static/js/legal/filters/CheckboxList.js | 40 +- .../static/js/legal/filters/CitationFilter.js | 295 ++- .../legal/filters/CitationRequireAllRadio.js | 40 +- fec/fec/static/js/legal/filters/DateFilter.js | 53 +- fec/fec/static/js/legal/filters/Dropdown.js | 20 +- fec/fec/static/js/legal/filters/TextFilter.js | 48 +- fec/fec/static/js/legal/requestorOptions.js | 45 +- fec/fec/static/js/modules/accessibility.js | 8 +- fec/fec/static/js/modules/analytics.js | 39 +- .../js/modules/audit-category-sub-category.js | 55 +- fec/fec/static/js/modules/audit_tags.js | 50 +- fec/fec/static/js/modules/calendar-helpers.js | 32 +- .../static/js/modules/calendar-list-view.js | 41 +- fec/fec/static/js/modules/calendar-tooltip.js | 11 +- fec/fec/static/js/modules/calendar.js | 95 +- fec/fec/static/js/modules/column-helpers.js | 180 +- fec/fec/static/js/modules/columns.js | 341 ++- fec/fec/static/js/modules/cycle-select.js | 25 +- fec/fec/static/js/modules/decoders.js | 125 +- fec/fec/static/js/modules/download.js | 39 +- fec/fec/static/js/modules/dropdowns.js | 89 +- fec/fec/static/js/modules/election-form.js | 26 +- fec/fec/static/js/modules/election-lookup.js | 8 +- fec/fec/static/js/modules/election-map.js | 51 +- fec/fec/static/js/modules/election-search.js | 78 +- fec/fec/static/js/modules/election-summary.js | 2 +- fec/fec/static/js/modules/election-utils.js | 37 +- fec/fec/static/js/modules/feedback.js | 23 +- fec/fec/static/js/modules/filings.js | 15 +- fec/fec/static/js/modules/filters-event.js | 9 +- .../js/modules/filters/checkbox-filter.js | 13 +- .../static/js/modules/filters/date-filter.js | 118 +- .../js/modules/filters/election-filter.js | 51 +- .../static/js/modules/filters/filter-base.js | 35 +- .../js/modules/filters/filter-control.js | 2 +- .../static/js/modules/filters/filter-panel.js | 23 +- .../static/js/modules/filters/filter-set.js | 58 +- .../static/js/modules/filters/filter-tags.js | 66 +- .../js/modules/filters/filter-typeahead.js | 83 +- .../static/js/modules/filters/multi-filter.js | 16 +- .../static/js/modules/filters/range-filter.js | 2 +- .../js/modules/filters/select-filter.js | 2 +- .../static/js/modules/filters/text-filter.js | 26 +- .../js/modules/filters/toggle-filter.js | 9 +- .../js/modules/filters/typeahead-filter.js | 18 +- .../modules/filters/validate-date-filters.js | 26 +- fec/fec/static/js/modules/fips.js | 23 +- fec/fec/static/js/modules/form-nav.js | 6 +- fec/fec/static/js/modules/helpers.js | 133 +- fec/fec/static/js/modules/home-events.js | 25 +- fec/fec/static/js/modules/keyword-modal.js | 24 +- fec/fec/static/js/modules/line-chart.js | 169 +- fec/fec/static/js/modules/listeners.js | 4 +- fec/fec/static/js/modules/maps-event.js | 16 +- fec/fec/static/js/modules/maps.js | 104 +- .../js/modules/other-spending-totals.js | 42 +- fec/fec/static/js/modules/performance.js | 23 +- fec/fec/static/js/modules/reaction-box.js | 7 +- fec/fec/static/js/modules/search.js | 11 +- fec/fec/static/js/modules/site-nav.js | 13 +- fec/fec/static/js/modules/skip-nav.js | 25 +- fec/fec/static/js/modules/table-columns.js | 88 +- fec/fec/static/js/modules/table-panels.js | 95 +- fec/fec/static/js/modules/table-switcher.js | 6 +- fec/fec/static/js/modules/tables.js | 358 ++-- fec/fec/static/js/modules/toc.js | 2 +- fec/fec/static/js/modules/toggle.js | 6 +- fec/fec/static/js/modules/top-entities.js | 80 +- fec/fec/static/js/modules/top-list.js | 1 - fec/fec/static/js/modules/typeahead.js | 63 +- fec/fec/static/js/modules/urls.js | 20 +- fec/fec/static/js/pages/calendar-page.js | 4 +- fec/fec/static/js/pages/candidate-single.js | 141 +- fec/fec/static/js/pages/committee-single.js | 422 ++-- fec/fec/static/js/pages/contact-form.js | 31 +- fec/fec/static/js/pages/data-landing.js | 17 +- fec/fec/static/js/pages/datatable-audit.js | 13 +- .../js/pages/datatable-candidates-office.js | 29 +- fec/fec/static/js/pages/datatable-filings.js | 18 +- .../datatable-individual-contributions.js | 2 +- fec/fec/static/js/pages/datatable-loans.js | 14 +- ...atatable-party-coordinated-expenditures.js | 16 +- fec/fec/static/js/pages/datatable-reports.js | 33 +- fec/fec/static/js/pages/elections.js | 29 +- fec/fec/static/js/pages/legal.js | 2 +- fec/fec/static/js/vendor/beautify-html.js | 1898 +++++++++-------- .../static/js/vendor/jquery.htmlClean.min.js | 721 ++++++- fec/fec/static/js/vendor/tablist.js | 16 +- package-lock.json | 1082 +++++++++- package.json | 11 + 111 files changed, 6438 insertions(+), 2902 deletions(-) create mode 100644 .eslintrc create mode 100644 .prettierrc delete mode 100644 fec/fec/static/js/legal/.eslintrc.yml diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000000..4845b397c0 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,32 @@ +{ + globals: { + 'document': true, + 'window': true, + 'context': true, + }, + rules: { + 'react/jsx-filename-extension': [0], + 'react/no-render-return-value': [0], + 'react/no-array-index-key': [0], + 'import/no-extraneous-dependencies': [0], + 'import/no-named-as-default': [0], // allow component to be the same as the default export + 'class-methods-use-this': [0], + 'no-throw-literal': [0], + 'camelcase': [0], + 'arrow-parens': [0], + 'object-curly-newline': [0], + 'no-var': [0], + 'comma-dangle': [0], + 'prettier/prettier': 'error', + }, + 'parserOptions': { + 'ecmaVersion': 6, + 'sourceType': 'module', + 'ecmaFeatures': { + 'jsx': true + } + }, + plugins: [ + 'prettier' + ] +} diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000000..be2983739f --- /dev/null +++ b/.prettierrc @@ -0,0 +1,4 @@ +{ + "singleQuote": true, + "jsxBracketSameLine": false +} diff --git a/fec/fec/static/js/data-init.js b/fec/fec/static/js/data-init.js index 95cd34bc03..c90e3f6355 100644 --- a/fec/fec/static/js/data-init.js +++ b/fec/fec/static/js/data-init.js @@ -31,7 +31,6 @@ var download = require('./modules/download'); var CycleSelect = require('./modules/cycle-select').CycleSelect; $(document).ready(function() { - $('.js-dropdown').each(function() { new dropdown.Dropdown(this); }); @@ -50,10 +49,14 @@ $(document).ready(function() { }); // Initialize glossary - new Glossary(terms, {}, { - termClass: 'glossary__term accordion__button', - definitionClass: 'glossary__definition accordion__content' - }); + new Glossary( + terms, + {}, + { + termClass: 'glossary__term accordion__button', + definitionClass: 'glossary__definition accordion__content' + } + ); // Initialize main search typeahead new typeahead.Typeahead('.js-search-input', 'allData', '/data/'); @@ -61,7 +64,6 @@ $(document).ready(function() { // Initialize header typeahead new typeahead.Typeahead($('.js-site-search'), 'all', '/data/'); - // Initialize feedback new feedback.Feedback(helpers.buildAppUrl(['issue'])); diff --git a/fec/fec/static/js/draftail/Anchor.js b/fec/fec/static/js/draftail/Anchor.js index 207e34915b..390fe5ac8c 100644 --- a/fec/fec/static/js/draftail/Anchor.js +++ b/fec/fec/static/js/draftail/Anchor.js @@ -19,16 +19,30 @@ class AnchorSource extends React.Component { var selectedText = currentContentBlock.getText().slice(start, end); // Uses the Draft.js API to create a new entity with the right data. - const contentWithEntity = content.createEntity(entityType.type, 'IMMUTABLE', { - anchor: slugify(selectedText), - }); + const contentWithEntity = content.createEntity( + entityType.type, + 'IMMUTABLE', + { + anchor: slugify(selectedText) + } + ); const entityKey = contentWithEntity.getLastCreatedEntityKey(); // Add some text for the entity to be activated on. const text = `${selectedText}`; - const newContent = Modifier.replaceText(content, selection, text, null, entityKey); - const nextState = EditorState.push(editorState, newContent, 'insert-characters'); + const newContent = Modifier.replaceText( + content, + selection, + text, + null, + entityKey + ); + const nextState = EditorState.push( + editorState, + newContent, + 'insert-characters' + ); onComplete(nextState); } @@ -40,7 +54,7 @@ class AnchorSource extends React.Component { // This adds additional 'term' class to the editor // to add custom editor styles inside customize-editor.css -const Anchor = ({children}) => ( +const Anchor = ({ children }) => ( { +const GlossarySelect = ({ handleChange, terms }) => { const options = terms.map(function(t, idx) { const disabled = idx === 0 ? true : false; const slug = `${slugify(t.term)}-${idx}`; @@ -19,22 +19,19 @@ const GlossarySelect = ({handleChange, terms}) => { }); return ( - handleChange(e.target.value)}> {options} ); -} +}; GlossarySelect.defaultProps = { handleChange: function() {}, terms: terms }; -const GlossaryEntity = ({handleChange, handleClose, title}) => ( - +const GlossaryEntity = ({ handleChange, handleClose, title }) => ( + ); @@ -68,17 +65,31 @@ class GlossarySource extends React.Component { var selectedText = currentContentBlock.getText().slice(start, end); // Uses the Draft.js API to create a new entity with the right data. - const contentWithEntity = content.createEntity(entityType.type, 'IMMUTABLE', { - term: value, - }); + const contentWithEntity = content.createEntity( + entityType.type, + 'IMMUTABLE', + { + term: value + } + ); const entityKey = contentWithEntity.getLastCreatedEntityKey(); // Add some text for the entity to be activated on. const text = `${selectedText}`; - const newContent = Modifier.replaceText(content, selection, text, null, entityKey); - const nextState = EditorState.push(editorState, newContent, 'insert-characters'); + const newContent = Modifier.replaceText( + content, + selection, + text, + null, + entityKey + ); + const nextState = EditorState.push( + editorState, + newContent, + 'insert-characters' + ); onComplete(nextState); } @@ -99,9 +110,7 @@ class GlossarySource extends React.Component { // This adds additional 'term' class to the editor // to add custom editor styles inside customize-editor.css -const Glossary = ({children}) => ( - {children} -); +const Glossary = ({ children }) => {children}; module.exports = { type: 'GLOSSARY', diff --git a/fec/fec/static/js/draftail/SansSerif.js b/fec/fec/static/js/draftail/SansSerif.js index 6b2864fada..54037d3f50 100644 --- a/fec/fec/static/js/draftail/SansSerif.js +++ b/fec/fec/static/js/draftail/SansSerif.js @@ -18,16 +18,30 @@ class SansserifSource extends React.Component { var selectedText = currentContentBlock.getText().slice(start, end); // Uses the Draft.js API to create a new entity with the right data. - const contentWithEntity = content.createEntity(entityType.type, 'IMMUTABLE', { - term: selectedText, - }); + const contentWithEntity = content.createEntity( + entityType.type, + 'IMMUTABLE', + { + term: selectedText + } + ); const entityKey = contentWithEntity.getLastCreatedEntityKey(); // Add some text for the entity to be activated on. const text = `${selectedText}`; - const newContent = Modifier.replaceText(content, selection, text, null, entityKey); - const nextState = EditorState.push(editorState, newContent, 'insert-characters'); + const newContent = Modifier.replaceText( + content, + selection, + text, + null, + entityKey + ); + const nextState = EditorState.push( + editorState, + newContent, + 'insert-characters' + ); onComplete(nextState); } @@ -37,15 +51,12 @@ class SansserifSource extends React.Component { } } - // This adds additional 'term' class to the editor // to add custom editor styles inside customize-editor.css -const Sansserif = ({children}) => ( - {children} -); +const Sansserif = ({ children }) => {children}; module.exports = { type: 'SANSSERIF', source: SansserifSource, - decorator: Sansserif, + decorator: Sansserif }; diff --git a/fec/fec/static/js/draftail/components/Modal.js b/fec/fec/static/js/draftail/components/Modal.js index c4cd41d66e..a13c7e71c4 100644 --- a/fec/fec/static/js/draftail/components/Modal.js +++ b/fec/fec/static/js/draftail/components/Modal.js @@ -1,8 +1,8 @@ import React from 'react'; -const Cancel = ({handleClose}) => ( +const Cancel = ({ handleClose }) => (
handleClose()} style={{ backgroundColor: 'rgb(51,51,51)', @@ -31,7 +31,7 @@ const Cancel = ({handleClose}) => (
); -const Title = ({title}) => ( +const Title = ({ title }) => (

(

); -const Modal = ({children, handleClose, title}) => ( +const Modal = ({ children, handleClose, title }) => (
( }} > - + <Title title={title} /> {children} </div> ); @@ -70,5 +70,4 @@ Modal.defaultProps = { title: '' }; - export default Modal; diff --git a/fec/fec/static/js/draftail/utils.js b/fec/fec/static/js/draftail/utils.js index 96f79a1440..3b102a4149 100644 --- a/fec/fec/static/js/draftail/utils.js +++ b/fec/fec/static/js/draftail/utils.js @@ -1,5 +1,7 @@ function slugify(text) { - return text.toString().toLowerCase() + return text + .toString() + .toLowerCase() .replace(/\s+/g, '-') .replace(/[^\w\-]+/g, '') .replace(/\-\-+/g, '-') diff --git a/fec/fec/static/js/init.js b/fec/fec/static/js/init.js index 39db5b2770..a04c289c33 100644 --- a/fec/fec/static/js/init.js +++ b/fec/fec/static/js/init.js @@ -26,12 +26,15 @@ var Sticky = require('component-sticky'); var FormNav = require('./modules/form-nav').FormNav; $(document).ready(function() { - // Initialize glossary - new Glossary(terms, {}, { - termClass: 'glossary__term accordion__button', - definitionClass: 'glossary__definition accordion__content' - }); + new Glossary( + terms, + {}, + { + termClass: 'glossary__term accordion__button', + definitionClass: 'glossary__definition accordion__content' + } + ); // Initialize new accordions $('.js-accordion').each(function() { diff --git a/fec/fec/static/js/legal.js b/fec/fec/static/js/legal.js index 8cda04292b..e1f8e4b48d 100644 --- a/fec/fec/static/js/legal.js +++ b/fec/fec/static/js/legal.js @@ -18,7 +18,9 @@ function KeywordModal() { this.elm = document.querySelector('.js-keyword-modal'); this.$elm = $(this.elm); this.$form = this.$elm.find('form'); - this.$fields = this.$elm.find('#keywords-any, #keywords-all, #keywords-exact'); + this.$fields = this.$elm.find( + '#keywords-any, #keywords-all, #keywords-exact' + ); this.$excludeField = this.$elm.find('#keywords-none'); this.$submit = this.$elm.find('button[type="submit"]'); this.$submit.on('click', this.handleSubmit.bind(this)); @@ -46,7 +48,9 @@ KeywordModal.prototype.handleSubmit = function(e) { e.preventDefault(); var combinedValue = this.combineFields(); this.$hiddenField.val(combinedValue); - this.$fields.each(function() { $(this).val(); }); + this.$fields.each(function() { + $(this).val(); + }); this.$excludeField.val(); this.$form.submit(); }; diff --git a/fec/fec/static/js/legal/.eslintrc.yml b/fec/fec/static/js/legal/.eslintrc.yml deleted file mode 100644 index f696e9c444..0000000000 --- a/fec/fec/static/js/legal/.eslintrc.yml +++ /dev/null @@ -1 +0,0 @@ -extends: react-app diff --git a/fec/fec/static/js/legal/FilterPanel.js b/fec/fec/static/js/legal/FilterPanel.js index 3c66395704..f2c96a56bb 100644 --- a/fec/fec/static/js/legal/FilterPanel.js +++ b/fec/fec/static/js/legal/FilterPanel.js @@ -1,27 +1,37 @@ const React = require('react'); class FilterPanel extends React.Component { -constructor(props) { - super(props); - this.state = {expanded: props.startOpen}; - this.toggle = this.toggle.bind(this); -} + constructor(props) { + super(props); + this.state = { expanded: props.startOpen }; + this.toggle = this.toggle.bind(this); + } -toggle() { - this.setState({'expanded': !this.state.expanded}); -} + toggle() { + this.setState({ expanded: !this.state.expanded }); + } -render() { - return <li> - <button className="accordion__button" aria-controls={this.props.id} aria-expanded={this.state.expanded} - onClick={this.toggle}> - {this.props.header} - </button> - <div className="accordion__content filters-inner" id={this.props.id} hidden={!this.state.expanded}> - {this.props.children} - </div> - </li> -} + render() { + return ( + <li> + <button + className="accordion__button" + aria-controls={this.props.id} + aria-expanded={this.state.expanded} + onClick={this.toggle} + > + {this.props.header} + </button> + <div + className="accordion__content filters-inner" + id={this.props.id} + hidden={!this.state.expanded} + > + {this.props.children} + </div> + </li> + ); + } } module.exports = FilterPanel; diff --git a/fec/fec/static/js/legal/Filters.js b/fec/fec/static/js/legal/Filters.js index f63fbb3450..24dc6cafdd 100644 --- a/fec/fec/static/js/legal/Filters.js +++ b/fec/fec/static/js/legal/Filters.js @@ -11,53 +11,132 @@ const requestorOptions = require('./requestorOptions'); const documentTypes = require('./documentTypes'); function Filters(props) { - const resultCountChange = props.query.resultCount - props.query.lastResultCount; + const resultCountChange = + props.query.resultCount - props.query.lastResultCount; - return <div> - <div className="accordion__content"> - <TextFilter key="ao_no" name="ao_no" label="AO number" value={props.query.ao_no} - handleChange={props.setQuery} getResults={props.getResults} /> - <TextFilter key="ao_requestor" name="ao_requestor" label="Requestor Name (or AO Name)" value={props.query.ao_requestor} - handleChange={props.setQuery} getResults={props.getResults} /> - <Dropdown key="ao_requestor_type" name="ao_requestor_type" label="Requestor Type" value={props.query.ao_requestor_type} - options={requestorOptions} handleChange={props.instantQuery} /> - </div> - <ul className="accordion--neutral" data-content-prefix="first"> - <FilterPanel id="first-content-0" header="Documents" startOpen={true}> - <TextFilter key="q" name="q" label="Keywords" value={props.query.q} - handleChange={props.setQuery} getResults={props.getResults} keywordModal={true} /> - <CheckboxFilter key="ao_is_pending" name="ao_is_pending" label="Show only pending requests" - checked={props.query.ao_is_pending} handleChange={props.instantQuery} /> - <CheckboxList key="ao_category" name="ao_category" label="Document Type" value={props.query.ao_category} - handleChange={props.instantQuery} options={documentTypes}/> - </FilterPanel> - <FilterPanel id="first-content-1" header="Citations"> - <CitationRequireAllRadio key="ao_citation_require_all" name="ao_citation_require_all" handleChange={props.instantQuery} - value={props.query.ao_citation_require_all} /> - <CitationFilter handleChange={props.setQuery} getResults={props.getResults} - key="ao_regulatory_citation" name="ao_regulatory_citation" label="Regulatory citation" instantQuery={props.instantQuery} - citationType="regulation" value={props.query.ao_regulatory_citation} - resultCountChange={resultCountChange} lastFilter={props.query.lastFilter}/> - <CitationFilter handleChange={props.setQuery} getResults={props.getResults} - key="ao_statutory_citation" name="ao_statutory_citation" label="Statutory citation" instantQuery={props.instantQuery} - citationType="statute" value={props.query.ao_statutory_citation} - resultCountChange={resultCountChange} lastFilter={props.query.lastFilter}/> - </FilterPanel> - <FilterPanel id="first-content-2" header="Time period"> - <DateFilter key="issue_date" label="Issued date" min_name="ao_min_issue_date" max_name="ao_max_issue_date" - min_value={props.query.ao_min_issue_date} max_value={props.query.ao_max_issue_date} - instantQuery={props.instantQuery} setQuery={props.setQuery} /> - <DateFilter key="request_date" label="Request date" min_name="ao_min_request_date" max_name="ao_max_request_date" - min_value={props.query.ao_min_request_date} max_value={props.query.ao_max_request_date} - instantQuery={props.instantQuery} setQuery={props.setQuery} /> - </FilterPanel> - <FilterPanel id="first-content-3" header="Other entities"> - <TextFilter key="ao_entity_name" name="ao_entity_name" label="Entity name" value={props.query.ao_entity_name} - handleChange={props.setQuery} getResults={props.getResults} - helpText="Search any individuals or groups involved in the opinion."/> - </FilterPanel> - </ul> - </div> + return ( + <div> + <div className="accordion__content"> + <TextFilter + key="ao_no" + name="ao_no" + label="AO number" + value={props.query.ao_no} + handleChange={props.setQuery} + getResults={props.getResults} + /> + <TextFilter + key="ao_requestor" + name="ao_requestor" + label="Requestor Name (or AO Name)" + value={props.query.ao_requestor} + handleChange={props.setQuery} + getResults={props.getResults} + /> + <Dropdown + key="ao_requestor_type" + name="ao_requestor_type" + label="Requestor Type" + value={props.query.ao_requestor_type} + options={requestorOptions} + handleChange={props.instantQuery} + /> + </div> + <ul className="accordion--neutral" data-content-prefix="first"> + <FilterPanel id="first-content-0" header="Documents" startOpen={true}> + <TextFilter + key="q" + name="q" + label="Keywords" + value={props.query.q} + handleChange={props.setQuery} + getResults={props.getResults} + keywordModal={true} + /> + <CheckboxFilter + key="ao_is_pending" + name="ao_is_pending" + label="Show only pending requests" + checked={props.query.ao_is_pending} + handleChange={props.instantQuery} + /> + <CheckboxList + key="ao_category" + name="ao_category" + label="Document Type" + value={props.query.ao_category} + handleChange={props.instantQuery} + options={documentTypes} + /> + </FilterPanel> + <FilterPanel id="first-content-1" header="Citations"> + <CitationRequireAllRadio + key="ao_citation_require_all" + name="ao_citation_require_all" + handleChange={props.instantQuery} + value={props.query.ao_citation_require_all} + /> + <CitationFilter + handleChange={props.setQuery} + getResults={props.getResults} + key="ao_regulatory_citation" + name="ao_regulatory_citation" + label="Regulatory citation" + instantQuery={props.instantQuery} + citationType="regulation" + value={props.query.ao_regulatory_citation} + resultCountChange={resultCountChange} + lastFilter={props.query.lastFilter} + /> + <CitationFilter + handleChange={props.setQuery} + getResults={props.getResults} + key="ao_statutory_citation" + name="ao_statutory_citation" + label="Statutory citation" + instantQuery={props.instantQuery} + citationType="statute" + value={props.query.ao_statutory_citation} + resultCountChange={resultCountChange} + lastFilter={props.query.lastFilter} + /> + </FilterPanel> + <FilterPanel id="first-content-2" header="Time period"> + <DateFilter + key="issue_date" + label="Issued date" + min_name="ao_min_issue_date" + max_name="ao_max_issue_date" + min_value={props.query.ao_min_issue_date} + max_value={props.query.ao_max_issue_date} + instantQuery={props.instantQuery} + setQuery={props.setQuery} + /> + <DateFilter + key="request_date" + label="Request date" + min_name="ao_min_request_date" + max_name="ao_max_request_date" + min_value={props.query.ao_min_request_date} + max_value={props.query.ao_max_request_date} + instantQuery={props.instantQuery} + setQuery={props.setQuery} + /> + </FilterPanel> + <FilterPanel id="first-content-3" header="Other entities"> + <TextFilter + key="ao_entity_name" + name="ao_entity_name" + label="Entity name" + value={props.query.ao_entity_name} + handleChange={props.setQuery} + getResults={props.getResults} + helpText="Search any individuals or groups involved in the opinion." + /> + </FilterPanel> + </ul> + </div> + ); } module.exports = Filters; diff --git a/fec/fec/static/js/legal/LegalApp.js b/fec/fec/static/js/legal/LegalApp.js index 4d63c2df5e..489297d4b4 100644 --- a/fec/fec/static/js/legal/LegalApp.js +++ b/fec/fec/static/js/legal/LegalApp.js @@ -2,6 +2,4 @@ const ReactDOM = require('react-dom'); const React = require('react'); const LegalSearch = require('./LegalSearch'); -ReactDOM.render( - <LegalSearch />, - document.getElementById('root')); +ReactDOM.render(<LegalSearch />, document.getElementById('root')); diff --git a/fec/fec/static/js/legal/LegalSearch.js b/fec/fec/static/js/legal/LegalSearch.js index 45b475d52f..6d472ad094 100644 --- a/fec/fec/static/js/legal/LegalSearch.js +++ b/fec/fec/static/js/legal/LegalSearch.js @@ -1,7 +1,6 @@ const $ = require('jquery'); const _ = require('underscore'); const React = require('react'); -const ReactDOM = require('react-dom'); const URI = require('urijs'); const Filters = require('./Filters'); const SearchResults = require('./SearchResults'); @@ -15,19 +14,30 @@ class LegalSearch extends React.Component { initState.q = initState.search; initState.type = initState.search_type; initState.advisory_opinions = []; - if(initState.ao_statutory_citation && !Array.isArray(initState.ao_statutory_citation)){ + if ( + initState.ao_statutory_citation && + !Array.isArray(initState.ao_statutory_citation) + ) { initState.ao_statutory_citation = [initState.ao_statutory_citation]; } - if(initState.ao_regulatory_citation && !Array.isArray(initState.ao_regulatory_citation)){ + if ( + initState.ao_regulatory_citation && + !Array.isArray(initState.ao_regulatory_citation) + ) { initState.ao_regulatory_citation = [initState.ao_regulatory_citation]; } - if(!initState.ao_category && !(initState.ao_is_pending && initState.ao_is_pending === 'true')) { - initState.ao_category = ['F']; - } else if(!Array.isArray(initState.ao_category)) { + if ( + !initState.ao_category && + !(initState.ao_is_pending && initState.ao_is_pending === 'true') + ) { + initState.ao_category = ['F']; + } else if (!Array.isArray(initState.ao_category)) { initState.ao_category = [initState.ao_category]; } - initState.from_hit = initState.from_hit ? parseInt(initState.from_hit, 10) : 0; + initState.from_hit = initState.from_hit + ? parseInt(initState.from_hit, 10) + : 0; initState.loading = true; this.getResults = this.getResults.bind(this); @@ -44,22 +54,23 @@ class LegalSearch extends React.Component { } setQuery(e, callback) { - const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value; - const newState = {[e.target.name]: value, lastFilter: e.target.name}; + const value = + e.target.type === 'checkbox' ? e.target.checked : e.target.value; + const newState = { [e.target.name]: value, lastFilter: e.target.name }; - if(e.target.name !== 'from_hit') { + if (e.target.name !== 'from_hit') { newState.from_hit = 0; } // special logic: pending AOs don't have final opinions, so if they are filtering // by pending, we remove all document type restrictions. Document type restrictions // can be added afterwards to filter by other document types. - if(e.target.name === 'ao_is_pending' && e.target.checked) { + if (e.target.name === 'ao_is_pending' && e.target.checked) { newState.ao_category = false; } this.setState(newState, () => { - if(callback) { + if (callback) { callback(); } }); @@ -73,14 +84,18 @@ class LegalSearch extends React.Component { const queryState = _.extend({}, state); queryState.search = queryState.q; Object.keys(queryState).forEach(function(queryParam) { - if(['advisory_opinions', - 'resultCount', - 'lastResultCount', - 'lastFilter', - 'loading', - 'lastQuery'].indexOf(queryParam) >=0 || + if ( + [ + 'advisory_opinions', + 'resultCount', + 'lastResultCount', + 'lastFilter', + 'loading', + 'lastQuery' + ].indexOf(queryParam) >= 0 || !queryState[queryParam] || - queryState[queryParam].length === 0) { + queryState[queryParam].length === 0 + ) { delete queryState[queryParam]; } }); @@ -88,63 +103,99 @@ class LegalSearch extends React.Component { } getResults(e) { - this.setState({'loading': true}); - if(e) { + this.setState({ loading: true }); + if (e) { e.preventDefault(); } let queryPath = URI(window.API_LOCATION) - .path([window.API_VERSION, 'legal', 'search'].join('/')) - .addQuery('api_key', window.API_KEY) - .addQuery('type', 'advisory_opinions'); + .path([window.API_VERSION, 'legal', 'search'].join('/')) + .addQuery('api_key', window.API_KEY) + .addQuery('type', 'advisory_opinions'); let queryState = this.getUserSearchCriteria(this.state); - Object.keys(queryState).forEach((queryParam) => { + Object.keys(queryState).forEach(queryParam => { queryPath = queryPath.addQuery(queryParam, queryState[queryParam]); }); const lastResultCount = this.state.resultCount; - $.getJSON(queryPath.toString(), (results) => { - this.setState({ advisory_opinions: results.advisory_opinions, - resultCount: results.total_advisory_opinions, - lastResultCount, - loading: false, - lastQuery: this.getUserSearchCriteria(this.state)}, () => { - queryPath = queryPath.removeSearch('api_key'); - window.history.pushState(URI.parseQuery(queryPath.query()), - null, queryPath.search().toString()); - }); - }); + $.getJSON(queryPath.toString(), results => { + this.setState( + { + advisory_opinions: results.advisory_opinions, + resultCount: results.total_advisory_opinions, + lastResultCount, + loading: false, + lastQuery: this.getUserSearchCriteria(this.state) + }, + () => { + queryPath = queryPath.removeSearch('api_key'); + window.history.pushState( + URI.parseQuery(queryPath.query()), + null, + queryPath.search().toString() + ); + } + ); + }); } render() { - return <section className="main__content--full data-container__wrapper"> - <div id="filters" className="filters is-open"> - <button className="filters__header filters__toggle js-filter-toggle" type="button"> - <span className="filters__title">Edit filters</span> - </button> - <div className="filters__content"> - <Filters query={this.state} setQuery={this.setQuery} - getResults={this.getResults} instantQuery={this.instantQuery} /> - </div> - </div> - <div id="results-aos" className="content__section data-container"> - <div className="data-container__widgets"> - <div className="data-container__head"> - <h1 className="data-container__title">Advisory opinions</h1> + return ( + <section className="main__content--full data-container__wrapper"> + <div id="filters" className="filters is-open"> + <button + className="filters__header filters__toggle js-filter-toggle" + type="button" + > + <span className="filters__title">Edit filters</span> + </button> + <div className="filters__content"> + <Filters + query={this.state} + setQuery={this.setQuery} + getResults={this.getResults} + instantQuery={this.instantQuery} + /> </div> - <Tags query={this.state.lastQuery} resultCount={this.state.resultCount} handleRemove={this.instantQuery} /> - <div className="u-padding--left u-padding--right"> - <div className="message message--info"> - <p>The advisory opinion search feature includes all FEC advisory opinions (AOs) to date. You can search all FEC AOs using keywords, AO numbers, names of requestors and more. For additional search filters, you can still search AOs using our legacy <a href="http://saos.fec.gov/saos/searchao">AO search</a>.</p> + </div> + <div id="results-aos" className="content__section data-container"> + <div className="data-container__widgets"> + <div className="data-container__head"> + <h1 className="data-container__title">Advisory opinions</h1> + </div> + <Tags + query={this.state.lastQuery} + resultCount={this.state.resultCount} + handleRemove={this.instantQuery} + /> + <div className="u-padding--left u-padding--right"> + <div className="message message--info"> + <p> + The advisory opinion search feature includes all FEC advisory + opinions (AOs) to date. You can search all FEC AOs + using keywords, AO numbers, names of requestors and more. For + additional search filters, you can still search AOs using our + legacy{' '} + <a href="http://saos.fec.gov/saos/searchao">AO search</a>. + </p> + </div> </div> </div> + <SearchResults + advisory_opinions={this.state.advisory_opinions} + q={this.state.q} + loading={this.state.loading} + /> + <Pagination + from_hit={this.state.from_hit} + advisory_opinions={this.state.advisory_opinions} + resultCount={this.state.resultCount} + handleChange={this.instantQuery} + /> </div> - <SearchResults advisory_opinions={this.state.advisory_opinions} q={this.state.q} loading={this.state.loading} /> - <Pagination from_hit={this.state.from_hit} advisory_opinions={this.state.advisory_opinions} - resultCount={this.state.resultCount} handleChange={this.instantQuery} /> - </div> - </section> -} + </section> + ); + } } module.exports = LegalSearch; diff --git a/fec/fec/static/js/legal/Pagination.js b/fec/fec/static/js/legal/Pagination.js index fc86f633bb..eae3a2224f 100644 --- a/fec/fec/static/js/legal/Pagination.js +++ b/fec/fec/static/js/legal/Pagination.js @@ -2,41 +2,59 @@ const React = require('react'); function Pagination(props) { function interceptHandleChange(offset) { - // from_hit is a zero-based index let updatedFromHit = props.from_hit + offset; // when from_hit is less than 0, reset to 0 (ensures no negative value) if (updatedFromHit < 0) { updatedFromHit = 0; - } + } // ensures that from_hit is less than result count else if (updatedFromHit >= props.resultCount) { updatedFromHit = props.resultCount - 1; } // create an event to update from_hit - const syntheticEvent = { - target: { - name: 'from_hit', - value: updatedFromHit + const syntheticEvent = { + target: { + name: 'from_hit', + value: updatedFromHit } }; props.handleChange(syntheticEvent); } - return props.resultCount > 0 ? <div className="results-info"> - <div className="dataTables_info"> - {props.from_hit + 1}–{props.from_hit + props.advisory_opinions.length} of {props.resultCount}</div> - <div className="dataTables_paginate"> - {props.from_hit > 0 ? <a className="paginate_button previous" - onClick={() => interceptHandleChange(-20)}>Previous</a> - : <span className="paginate_button previous is-disabled">Previous</span> } - {props.from_hit + props.advisory_opinions.length < props.resultCount ? <a className="paginate_button next" - onClick={() => interceptHandleChange(20)}>Next</a> - : <span className="paginate_button next is-disabled">Next</span> } - </div> - </div> : null + return props.resultCount > 0 ? ( + <div className="results-info"> + <div className="dataTables_info"> + {props.from_hit + 1} + – + {props.from_hit + props.advisory_opinions.length} of {props.resultCount} + </div> + <div className="dataTables_paginate"> + {props.from_hit > 0 ? ( + <a + className="paginate_button previous" + onClick={() => interceptHandleChange(-20)} + > + Previous + </a> + ) : ( + <span className="paginate_button previous is-disabled">Previous</span> + )} + {props.from_hit + props.advisory_opinions.length < props.resultCount ? ( + <a + className="paginate_button next" + onClick={() => interceptHandleChange(20)} + > + Next + </a> + ) : ( + <span className="paginate_button next is-disabled">Next</span> + )} + </div> + </div> + ) : null; } module.exports = Pagination; diff --git a/fec/fec/static/js/legal/SearchResults.js b/fec/fec/static/js/legal/SearchResults.js index ce39414a34..c199cab932 100644 --- a/fec/fec/static/js/legal/SearchResults.js +++ b/fec/fec/static/js/legal/SearchResults.js @@ -2,78 +2,162 @@ const React = require('react'); const $ = require('jquery'); const moment = require('moment'); - function SearchResults(props) { - function highlights(advisory_opinion) { - return {__html: '…' + advisory_opinion.highlights } - } + // function highlights(advisory_opinion) { + // return { __html: '…' + advisory_opinion.highlights }; + // } function advisoryOpinionLink(no) { - return $('#advisory_opinion_page').val().replace('ao_no', no) + return $('#advisory_opinion_page') + .val() + .replace('ao_no', no); } function getIssueDate(advisory_opinion) { - if(advisory_opinion.status === 'Pending') { - return "Pending"; + if (advisory_opinion.status === 'Pending') { + return 'Pending'; } else if (advisory_opinion.status === 'Withdrawn') { - return "Withdrawn"; + return 'Withdrawn'; } else { - return moment(advisory_opinion.issue_date).format('MM/DD/YYYY'); + return moment(advisory_opinion.issue_date).format('MM/DD/YYYY'); } } - if(props.loading) { - return <div className="overlay__container"> - <div className="overlay is-loading"></div> - </div> + if (props.loading) { + return ( + <div className="overlay__container"> + <div className="overlay is-loading" /> + </div> + ); } else { - if(props.advisory_opinions && props.advisory_opinions.length > 0) { - return <div className="data-container__datatable"> - <table className="simple-table simple-table--display"> - <thead className="simple-table__header"> - <tr> - <th scope="col" className="simple-table__header-cell">Case</th> - <th scope="col" className="simple-table__header-cell">Date issued</th> - <th scope="col" className="simple-table__header-cell">Summary</th> - <th scope="col" className="simple-table__header-cell">This opinion is cited by these later opinions</th> - </tr> - </thead> - <tbody> - { props.advisory_opinions.map((advisory_opinion) => { - return <tr key={"CASE " + advisory_opinion.no} className="simple-table__row"> - <td scope="row" className="simple-table__cell"><div><i className="icon i-folder icon--inline--left"></i><strong> - <a href={advisoryOpinionLink(advisory_opinion.no)}>AO {advisory_opinion.no}</a></strong></div> - <div><a href={advisoryOpinionLink(advisory_opinion.no)}>{advisory_opinion.name}</a></div> - { advisory_opinion.is_pending && <div><i className="icon pending-ao__icon icon--inline--left"></i>Pending request</div> } - </td> - <td className="simple-table__cell">{getIssueDate(advisory_opinion)}</td> - <td className="simple-table__cell">{advisory_opinion.summary}</td> - <td className="simple-table__cell"> - {advisory_opinion.aos_cited_by.length > 0 ? advisory_opinion.aos_cited_by.map((citation) => { - return <div key={"CITATION" + citation.no}><a href={advisoryOpinionLink(citation.no)}>{citation.no}</a></div> - }) : "This advisory opinion is not cited by other advisory opinions"} - </td> + if (props.advisory_opinions && props.advisory_opinions.length > 0) { + return ( + <div className="data-container__datatable"> + <table className="simple-table simple-table--display"> + <thead className="simple-table__header"> + <tr> + <th scope="col" className="simple-table__header-cell"> + Case + </th> + <th scope="col" className="simple-table__header-cell"> + Date issued + </th> + <th scope="col" className="simple-table__header-cell"> + Summary + </th> + <th scope="col" className="simple-table__header-cell"> + This opinion is cited by these later opinions + </th> </tr> - {advisory_opinion.highlights.length > 0 && <tr><td scope="row" className="simple-table__cell">Keyword matches in documents</td> - <td colSpan="3" className="t-serif legal-search-result__hit u-padding--top simple-table__cell" - dangerouslySetInnerHTML={ highlights(advisory_opinion) }></td></tr>} - }) } - </tbody> - </table> + </thead> + <tbody> + {props.advisory_opinions.map(advisory_opinion => { + return ( + <tr + key={'CASE ' + advisory_opinion.no} + className="simple-table__row" + > + <td className="simple-table__cell"> + <div> + <i className="icon i-folder icon--inline--left" /> + <strong> + <a href={advisoryOpinionLink(advisory_opinion.no)}> + AO {advisory_opinion.no} + </a> + </strong> + </div> + <div> + <a href={advisoryOpinionLink(advisory_opinion.no)}> + {advisory_opinion.name} + </a> + </div> + {advisory_opinion.is_pending && ( + <div> + <i className="icon pending-ao__icon icon--inline--left" /> + Pending request + </div> + )} + </td> + <td className="simple-table__cell"> + {getIssueDate(advisory_opinion)} + </td> + <td className="simple-table__cell"> + {advisory_opinion.summary} + </td> + <td className="simple-table__cell"> + {advisory_opinion.aos_cited_by.length > 0 + ? advisory_opinion.aos_cited_by.map(citation => { + return ( + <div key={'CITATION' + citation.no}> + <a href={advisoryOpinionLink(citation.no)}> + {citation.no} + </a> + </div> + ); + }) + : 'This advisory opinion is not cited by other advisory opinions'} + </td> + </tr> + ); + // { + // advisory_opinion.highlights.length > 0 && ( + // <tr> + // <td scope="row" className="simple-table__cell"> + // Keyword matches in documents + // </td> + // <td + // colSpan="3" + // className="t-serif legal-search-result__hit u-padding--top simple-table__cell" + // dangerouslySetInnerHTML={highlights(advisory_opinion)} + // /> + // </tr> + // ); + // } + })} + </tbody> + </table> </div> + ); } else { - return <div className="message message--no-icon"> - <h2 className="message__title">No results</h2> - <p>Sorry, we didn’t find any documents matching { props.query }.</p> - <div className="message--alert__bottom"> - <p>Think this was a mistake?</p> - <ul className="list--buttons"> - {props.query && <li><a className="button button--standard" href="http://search04.fec.gov/vivisimo/cgi-bin/query-meta?v%3Asources=Administrative_Fine%2CAdvisory_Opinion%2CAlternative_Dispute_Resolution%2CAudit_Reports%2CMatters_Under_Review%2CMatters_Under_Review_Archived%2CRulemaster%2CCandidate_Summary%2CCommittee_Summary%2Cfec.gov&query={{ query }}&x=0&y=0&v%3aproject=fec_search_02_prj&v%3aframe=form&form=advanced-fec&">Try FEC.gov</a></li>} - <li><a className="button button--standard" href={ "mailto:" + $('#contact-email').val() }>Email our team</a></li> - <li><a className="button button--standard" href="https://github.com/fecgov/fec/issues">File an issue</a></li> - </ul> + return ( + <div className="message message--no-icon"> + <h2 className="message__title">No results</h2> + <p> + Sorry, we didn’t find any documents matching {props.query}. + </p> + <div className="message--alert__bottom"> + <p>Think this was a mistake?</p> + <ul className="list--buttons"> + {props.query && ( + <li> + <a + className="button button--standard" + href="http://search04.fec.gov/vivisimo/cgi-bin/query-meta?v%3Asources=Administrative_Fine%2CAdvisory_Opinion%2CAlternative_Dispute_Resolution%2CAudit_Reports%2CMatters_Under_Review%2CMatters_Under_Review_Archived%2CRulemaster%2CCandidate_Summary%2CCommittee_Summary%2Cfec.gov&query={{ query }}&x=0&y=0&v%3aproject=fec_search_02_prj&v%3aframe=form&form=advanced-fec&" + > + Try FEC.gov + </a> + </li> + )} + <li> + <a + className="button button--standard" + href={'mailto:' + $('#contact-email').val()} + > + Email our team + </a> + </li> + <li> + <a + className="button button--standard" + href="https://github.com/fecgov/fec/issues" + > + File an issue + </a> + </li> + </ul> + </div> </div> - </div> + ); } } } diff --git a/fec/fec/static/js/legal/Tags.js b/fec/fec/static/js/legal/Tags.js index 7df0b124d1..2500b185be 100644 --- a/fec/fec/static/js/legal/Tags.js +++ b/fec/fec/static/js/legal/Tags.js @@ -3,24 +3,25 @@ const _ = require('underscore'); const requestorOptions = require('./requestorOptions'); const documentTypes = require('./documentTypes'); - function Tags(props) { const namedFields = []; Array.prototype.push.apply(namedFields, requestorOptions); Array.prototype.push.apply(namedFields, documentTypes); - namedFields.push({value: 'ao_status', text: 'Pending'}); - var citationRequireAll = props.query.ao_citation_require_all && props.query.ao_citation_require_all !== 'false'; + namedFields.push({ value: 'ao_status', text: 'Pending' }); + var citationRequireAll = + props.query.ao_citation_require_all && + props.query.ao_citation_require_all !== 'false'; function removeQuery(name, value) { function handleRemove(e) { var newEvent; - if(Array.isArray(props.query[name])) { + if (Array.isArray(props.query[name])) { let currentValues = _.extend(props.query[name]); currentValues.splice(currentValues.indexOf(value), 1); - if(currentValues.length === 0) { - newEvent = {target: { name, value: false } }; + if (currentValues.length === 0) { + newEvent = { target: { name, value: false } }; } else { - newEvent = { target: { name, value: currentValues } } + newEvent = { target: { name, value: currentValues } }; } } else { newEvent = { target: { name, value: false } }; @@ -33,7 +34,7 @@ function Tags(props) { function getTag(name, value) { var tagText; - if(["true", "false", true, false].indexOf(value) >= 0) { + if (['true', 'false', true, false].indexOf(value) >= 0) { value = name; } @@ -43,72 +44,121 @@ function Tags(props) { tagText = value; } - return <div key={name + '-' + tagText} className="tag__item">{tagText} - <button className="button tag__remove" onClick={removeQuery(name, value)}><span className="u-visually-hidden">Remove</span></button> + return ( + <div key={name + '-' + tagText} className="tag__item"> + {tagText} + <button + className="button tag__remove" + onClick={removeQuery(name, value)} + > + <span className="u-visually-hidden">Remove</span> + </button> </div> + ); } function getTagCategory(query, tagName) { - if(Array.isArray(query[tagName])) { + if (Array.isArray(query[tagName])) { function getArrayTagClasses() { - var classes = "tag__category"; - if(tagName.indexOf('citation') >= 0 && citationRequireAll) { - classes += " tag__category--and"; + var classes = 'tag__category'; + if (tagName.indexOf('citation') >= 0 && citationRequireAll) { + classes += ' tag__category--and'; } return classes; } - return <li key={tagName} className={getArrayTagClasses()}>{query[tagName].map(val => getTag(tagName, val))}</li> - } else if(tagName.indexOf("date") >= 0) { - if(query[tagName].min && query[tagName].max) { - return <li key={tagName} className="tag__category tag__category__range--date"> - {[getTag(query[tagName].minName, query[tagName].min), getTag(query[tagName].maxName, query[tagName].max)]}</li> - } else if(query[tagName].min) { - return <li key={tagName} className="tag__category">{getTag(query[tagName].minName, 'Beginning ' + query[tagName].min)}</li> + return ( + <li key={tagName} className={getArrayTagClasses()}> + {query[tagName].map(val => getTag(tagName, val))} + </li> + ); + } else if (tagName.indexOf('date') >= 0) { + if (query[tagName].min && query[tagName].max) { + return ( + <li + key={tagName} + className="tag__category tag__category__range--date" + > + {[ + getTag(query[tagName].minName, query[tagName].min), + getTag(query[tagName].maxName, query[tagName].max) + ]} + </li> + ); + } else if (query[tagName].min) { + return ( + <li key={tagName} className="tag__category"> + {getTag(query[tagName].minName, 'Beginning ' + query[tagName].min)} + </li> + ); } else { - return <li key={tagName} className="tag__category">{getTag(query[tagName].maxName, 'Ending ' + query[tagName].max)}</li> + return ( + <li key={tagName} className="tag__category"> + {getTag(query[tagName].maxName, 'Ending ' + query[tagName].max)} + </li> + ); } } else { - return <li key={tagName} className="tag__category">{getTag(tagName, query[tagName])}</li>; + return ( + <li key={tagName} className="tag__category"> + {getTag(tagName, query[tagName])} + </li> + ); } } function getTagCategories() { let query = _.extend({}, props.query); - const excludedFields = ['from_hit', 'search', 'type', 'search_type', 'ao_citation_require_all'] + const excludedFields = [ + 'from_hit', + 'search', + 'type', + 'search_type', + 'ao_citation_require_all' + ]; excludedFields.forEach(fieldName => delete query[fieldName]); - if(query.ao_min_issue_date || query.ao_max_issue_date) { - query.issue_date = {min: query.ao_min_issue_date, - minName: 'ao_min_issue_date', - max: query.ao_max_issue_date, - maxName: 'ao_max_issue_date'}; + if (query.ao_min_issue_date || query.ao_max_issue_date) { + query.issue_date = { + min: query.ao_min_issue_date, + minName: 'ao_min_issue_date', + max: query.ao_max_issue_date, + maxName: 'ao_max_issue_date' + }; delete query.ao_min_issue_date; - delete query.ao_max_issue_date + delete query.ao_max_issue_date; } - if(query.ao_min_request_date || query.ao_max_request_date) { - query.request_date = {min: query.ao_min_request_date, - minName: 'ao_min_request_date', - max: query.ao_max_request_date, - maxName: 'ao_max_request_date'}; - delete query.ao_min_request_date - delete query.ao_max_request_date + if (query.ao_min_request_date || query.ao_max_request_date) { + query.request_date = { + min: query.ao_min_request_date, + minName: 'ao_min_request_date', + max: query.ao_max_request_date, + maxName: 'ao_max_request_date' + }; + delete query.ao_min_request_date; + delete query.ao_max_request_date; } - return Object.keys(query).map((tagName) => { - return getTagCategory(query, tagName) - + return Object.keys(query).map(tagName => { + return getTagCategory(query, tagName); }); } - if(getTagCategories()) { - return <div className="data-container__tags"><div className="row"><h3 className="tags__title"> - Viewing <span className="tags__count">{props.resultCount}</span> {(getTagCategories().length > 0) ? " filtered results for:" : " results"} - </h3></div> - <ul className="tags"> - {getTagCategories()} - </ul></div> - } else { - return; - } + if (getTagCategories()) { + return ( + <div className="data-container__tags"> + <div className="row"> + <h3 className="tags__title"> + Viewing <span className="tags__count">{props.resultCount}</span>{' '} + {getTagCategories().length > 0 + ? ' filtered results for:' + : ' results'} + </h3> + </div> + <ul className="tags">{getTagCategories()}</ul> + </div> + ); + } else { + return; + } } -module.exports = Tags +module.exports = Tags; diff --git a/fec/fec/static/js/legal/documentTypes.js b/fec/fec/static/js/legal/documentTypes.js index 85ed4e482e..840ca472d4 100644 --- a/fec/fec/static/js/legal/documentTypes.js +++ b/fec/fec/static/js/legal/documentTypes.js @@ -1,7 +1,12 @@ -module.exports = [{'value' : 'F', 'text': 'Final Opinion'}, - {'value' : 'V', 'text': 'Votes'}, - {'value' : 'D', 'text': 'Draft Documents'}, - {'value' : 'R', 'text': 'AO Request, Supplemental Material, and Extensions of Time'}, - {'value' : 'W', 'text': 'Withdrawal of Request'}, - {'value' : 'C', 'text': 'Comments and Ex parte Communications'}, - {'value' : 'S', 'text': 'Commissioner Statements'}]; +module.exports = [ + { value: 'F', text: 'Final Opinion' }, + { value: 'V', text: 'Votes' }, + { value: 'D', text: 'Draft Documents' }, + { + value: 'R', + text: 'AO Request, Supplemental Material, and Extensions of Time' + }, + { value: 'W', text: 'Withdrawal of Request' }, + { value: 'C', text: 'Comments and Ex parte Communications' }, + { value: 'S', text: 'Commissioner Statements' } +]; diff --git a/fec/fec/static/js/legal/filters/Checkbox.js b/fec/fec/static/js/legal/filters/Checkbox.js index 04c0b0c8a8..0a487ac6cd 100644 --- a/fec/fec/static/js/legal/filters/Checkbox.js +++ b/fec/fec/static/js/legal/filters/Checkbox.js @@ -1,9 +1,18 @@ const React = require('react'); function Checkbox(props) { - return <div><input type="checkbox" id={props.name} name={props.name} - onChange={props.handleChange} checked={props.checked} /> - <label htmlFor={props.name}>{props.label}</label></div> + return ( + <div> + <input + type="checkbox" + id={props.name} + name={props.name} + onChange={props.handleChange} + checked={props.checked} + /> + <label htmlFor={props.name}>{props.label}</label> + </div> + ); } module.exports = Checkbox; diff --git a/fec/fec/static/js/legal/filters/CheckboxFilter.js b/fec/fec/static/js/legal/filters/CheckboxFilter.js index 168d856066..9fff11dc7b 100644 --- a/fec/fec/static/js/legal/filters/CheckboxFilter.js +++ b/fec/fec/static/js/legal/filters/CheckboxFilter.js @@ -2,10 +2,16 @@ const React = require('react'); const Checkbox = require('./Checkbox'); function CheckboxFilter(props) { - return <div className="filter"> - <Checkbox name={props.name} handleChange={props.handleChange} - checked={props.checked || false} label={props.label} /> + return ( + <div className="filter"> + <Checkbox + name={props.name} + handleChange={props.handleChange} + checked={props.checked || false} + label={props.label} + /> </div> + ); } module.exports = CheckboxFilter; diff --git a/fec/fec/static/js/legal/filters/CheckboxList.js b/fec/fec/static/js/legal/filters/CheckboxList.js index c94e73b9c4..62885f2a98 100644 --- a/fec/fec/static/js/legal/filters/CheckboxList.js +++ b/fec/fec/static/js/legal/filters/CheckboxList.js @@ -1,36 +1,46 @@ const React = require('react'); - function CheckboxList(props) { function handleCheckboxListChange(e) { - const newEvent = { target: { name: props.name, value: props.value} }; + const newEvent = { target: { name: props.name, value: props.value } }; // value should be set to an array of values. If the array is empty, // it will be removed from query in the LegalSearch compenent. if (newEvent.target.value) { - if(newEvent.target.value.indexOf(e.target.value) >= 0) { + if (newEvent.target.value.indexOf(e.target.value) >= 0) { newEvent.target.value.splice(props.value.indexOf(e.target.value), 1); } else { newEvent.target.value.push(e.target.value); } } else { - newEvent.target.value = [e.target.value]; + newEvent.target.value = [e.target.value]; } props.handleChange(newEvent); } - return <div className="filter"> - <legend className="label">{props.label}</legend> - {props.options.map((option) => { - return <div key={option.value}> - <input type="checkbox" id={`${props.name}_${option.value}`} name={props.name} - checked={props.value && props.value.indexOf(option.value) >= 0} value={option.value} onChange={handleCheckboxListChange} /> - <label htmlFor={`${props.name}_${option.value}`}>{option.text}</label> - </div> - })} - </div> - + return ( + <div className="filter"> + <legend className="label">{props.label}</legend> + {props.options.map(option => { + return ( + <div key={option.value}> + <input + type="checkbox" + id={`${props.name}_${option.value}`} + name={props.name} + checked={props.value && props.value.indexOf(option.value) >= 0} + value={option.value} + onChange={handleCheckboxListChange} + /> + <label htmlFor={`${props.name}_${option.value}`}> + {option.text} + </label> + </div> + ); + })} + </div> + ); } module.exports = CheckboxList; diff --git a/fec/fec/static/js/legal/filters/CitationFilter.js b/fec/fec/static/js/legal/filters/CitationFilter.js index 6d8b5f58bd..7708c9f556 100644 --- a/fec/fec/static/js/legal/filters/CitationFilter.js +++ b/fec/fec/static/js/legal/filters/CitationFilter.js @@ -4,129 +4,216 @@ const $ = require('jquery'); const Checkbox = require('./Checkbox'); class CitationFilter extends React.Component { - constructor(props) { - super(props); - this.state = { citations: [], dropdownVisible: false, currentValue: '', highlightCitation: 0 } - this.interceptChange = this.interceptChange.bind(this); - this.setSelection = this.setSelection.bind(this); - this.dropdownDisplay = this.dropdownDisplay.bind(this); - this.hideDropdown = this.hideDropdown.bind(this); - this.getFilterAddedText = this.getFilterAddedText.bind(this); - this.handleKeydown = this.handleKeydown.bind(this); - this.handleMouseOver = this.handleMouseOver.bind(this); - } - - interceptChange(e) { - if(e.target.value) { - const path = URI(window.API_LOCATION) - .path([window.API_VERSION, 'legal', 'citation', this.props.citationType, e.target.value].join('/')) - .addQuery('api_key', window.API_KEY); - $.getJSON(path.toString(), (result) => { - this.setState({citations: result.citations, dropdownVisible: true}); - }); - } else { - this.setState({citations: [], dropdownVisible: false}); - } + constructor(props) { + super(props); + this.state = { + citations: [], + dropdownVisible: false, + currentValue: '', + highlightCitation: 0 + }; + this.interceptChange = this.interceptChange.bind(this); + this.setSelection = this.setSelection.bind(this); + this.dropdownDisplay = this.dropdownDisplay.bind(this); + this.hideDropdown = this.hideDropdown.bind(this); + this.getFilterAddedText = this.getFilterAddedText.bind(this); + this.handleKeydown = this.handleKeydown.bind(this); + this.handleMouseOver = this.handleMouseOver.bind(this); + } - this.setState({currentValue: e.target.value, highlightCitation: 0}); + interceptChange(e) { + if (e.target.value) { + const path = URI(window.API_LOCATION) + .path( + [ + window.API_VERSION, + 'legal', + 'citation', + this.props.citationType, + e.target.value + ].join('/') + ) + .addQuery('api_key', window.API_KEY); + $.getJSON(path.toString(), result => { + this.setState({ citations: result.citations, dropdownVisible: true }); + }); + } else { + this.setState({ citations: [], dropdownVisible: false }); } - setSelection(citation) { - const citationTags = this.props.value || []; - citationTags.push(citation) - const syntheticEvent = { target: {name: this.props.name, value: citationTags } }; - this.props.instantQuery(syntheticEvent); - this.setState({dropdownVisible: false, currentValue: '', lastAction: "added"}); - } + this.setState({ currentValue: e.target.value, highlightCitation: 0 }); + } - dropdownDisplay() { - return this.state.dropdownVisible ? 'block' : 'none'; - } + setSelection(citation) { + const citationTags = this.props.value || []; + citationTags.push(citation); + const syntheticEvent = { + target: { name: this.props.name, value: citationTags } + }; + this.props.instantQuery(syntheticEvent); + this.setState({ + dropdownVisible: false, + currentValue: '', + lastAction: 'added' + }); + } - hideDropdown(e) { - this.setState({dropdownVisible: false}); - } + dropdownDisplay() { + return this.state.dropdownVisible ? 'block' : 'none'; + } - removeCitation(citationText) { - const value = this.props.value; - value.splice(this.props.value.indexOf(citationText), 1); - const syntheticEvent = {target: { name: this.props.name, - value } } - this.props.instantQuery(syntheticEvent); - this.setState({lastAction: "removed"}); - } + hideDropdown(e) { + this.setState({ dropdownVisible: false }); + } - getFilterAddedText() { - if(this.props.resultCountChange < 0) { - return (-this.props.resultCountChange).toString() + " fewer results." - } else if(this.props.resultCountChange > 0) { - return this.props.resultCountChange.toString() + " more results." - } else { - return "Same number of results." + removeCitation(citationText) { + const value = this.props.value; + value.splice(this.props.value.indexOf(citationText), 1); + const syntheticEvent = { + target: { + name: this.props.name, + value } - } + }; + this.props.instantQuery(syntheticEvent); + this.setState({ lastAction: 'removed' }); + } - handleMouseOver(index) { - return (e) => { - this.setState({ highlightCitation: index }); - } + getFilterAddedText() { + if (this.props.resultCountChange < 0) { + return (-this.props.resultCountChange).toString() + ' fewer results.'; + } else if (this.props.resultCountChange > 0) { + return this.props.resultCountChange.toString() + ' more results.'; + } else { + return 'Same number of results.'; } + } - handleKeydown(e) { - let highlightCitation = this.state.highlightCitation; + handleMouseOver(index) { + return e => { + this.setState({ highlightCitation: index }); + }; + } - if(this.state.dropdownVisible) { - // down arrow or tab - if(e.keyCode === 40 || e.keyCode === 9) { - e.preventDefault(); - if(highlightCitation < this.state.citations.length - 1) { - this.setState({ highlightCitation: highlightCitation + 1 }); - } else { - this.setState({ highlightCitation: 0 }); - } - } + handleKeydown(e) { + let highlightCitation = this.state.highlightCitation; - // up arrow - if(e.keyCode === 38) { - if(highlightCitation > 0) { - this.setState({ highlightCitation: highlightCitation - 1 }); - } else { - this.setState({ highlightCitation: this.state.citations.length - 1 }); - } + if (this.state.dropdownVisible) { + // down arrow or tab + if (e.keyCode === 40 || e.keyCode === 9) { + e.preventDefault(); + if (highlightCitation < this.state.citations.length - 1) { + this.setState({ highlightCitation: highlightCitation + 1 }); + } else { + this.setState({ highlightCitation: 0 }); } + } - // enter - if(e.keyCode === 13) { - this.setSelection(this.state.citations[this.state.highlightCitation].citation_text); + // up arrow + if (e.keyCode === 38) { + if (highlightCitation > 0) { + this.setState({ highlightCitation: highlightCitation - 1 }); + } else { + this.setState({ highlightCitation: this.state.citations.length - 1 }); } } + + // enter + if (e.keyCode === 13) { + this.setSelection( + this.state.citations[this.state.highlightCitation].citation_text + ); + } } + } - render() { - return <div className="filter" onBlur={this.hideDropdown} onKeyDown={this.handleKeydown}> - <label className="label" htmlFor={this.props.name + "-filter"}>{this.props.label}</label> - {this.props.value && this.props.value.map(citationText => { - return <Checkbox key={citationText} name={citationText} label={citationText} - checked={true} handleChange={() => this.removeCitation(citationText)} /> + render() { + return ( + <div + className="filter" + onBlur={this.hideDropdown} + onKeyDown={this.handleKeydown} + > + <label className="label" htmlFor={this.props.name + '-filter'}> + {this.props.label} + </label> + {this.props.value && + this.props.value.map(citationText => { + return ( + <Checkbox + key={citationText} + name={citationText} + label={citationText} + checked={true} + handleChange={() => this.removeCitation(citationText)} + /> + ); })} - {this.props.lastFilter === this.props.name && - <div className="filter__message filter__message--success"><div>Filter {this.state.lastAction} ✓</div><div>{this.getFilterAddedText()}</div></div>} - <div className="combo combo--search--mini" style={{position: 'relative', display: 'block'}}> - <input id={this.props.name + "-filter"} type="text" name={this.props.name} className="combo__input" - value={this.state.currentValue} onChange={this.interceptChange} /> - <div className="tt-menu" aria-live="polite" - style={{position: 'absolute', top: '100%', left: '0px', zIndex: 100, display: this.dropdownDisplay()}}> - <div className="tt-dataset tt-dataset-candidate"> - <span className="tt-suggestion__header">Select a citation:</span> - {this.state.citations.map((citation, index) => { - return <div key={citation.citation_text} onMouseDown={() => this.setSelection(citation.citation_text)} - onMouseOver={this.handleMouseOver(index)} className="selectCitation"> - <span className={"tt-suggestion tt-selectable" + (this.state.highlightCitation === index && ' tt-cursor')}> - <span className="tt-suggestion__name">{citation.citation_text}</span> - {citation.formerly && <span className="tt-suggestion__office">(formerly {citation.formerly})</span>} - </span></div> - })} - </div></div></div></div> -}} + {this.props.lastFilter === this.props.name && ( + <div className="filter__message filter__message--success"> + <div>Filter {this.state.lastAction} ✓</div> + <div>{this.getFilterAddedText()}</div> + </div> + )} + <div + className="combo combo--search--mini" + style={{ position: 'relative', display: 'block' }} + > + <input + id={this.props.name + '-filter'} + type="text" + name={this.props.name} + className="combo__input" + value={this.state.currentValue} + onChange={this.interceptChange} + /> + <div + className="tt-menu" + aria-live="polite" + style={{ + position: 'absolute', + top: '100%', + left: '0px', + zIndex: 100, + display: this.dropdownDisplay() + }} + > + <div className="tt-dataset tt-dataset-candidate"> + <span className="tt-suggestion__header">Select a citation:</span> + {this.state.citations.map((citation, index) => { + return ( + <div + key={citation.citation_text} + onMouseDown={() => + this.setSelection(citation.citation_text) + } + onMouseOver={this.handleMouseOver(index)} + className="selectCitation" + > + <span + className={ + 'tt-suggestion tt-selectable' + + (this.state.highlightCitation === index && ' tt-cursor') + } + > + <span className="tt-suggestion__name"> + {citation.citation_text} + </span> + {citation.formerly && ( + <span className="tt-suggestion__office"> + (formerly {citation.formerly}) + </span> + )} + </span> + </div> + ); + })} + </div> + </div> + </div> + </div> + ); + } +} module.exports = CitationFilter; diff --git a/fec/fec/static/js/legal/filters/CitationRequireAllRadio.js b/fec/fec/static/js/legal/filters/CitationRequireAllRadio.js index 7e4a96716f..4085b19d99 100644 --- a/fec/fec/static/js/legal/filters/CitationRequireAllRadio.js +++ b/fec/fec/static/js/legal/filters/CitationRequireAllRadio.js @@ -1,16 +1,34 @@ -const React = require('react') +const React = require('react'); function CitationRequireAllRadio(props) { - const require_all = props.value === "true"; - return <div> - <label>If more than one citation is entered:</label> - <input type="radio" id="citation_require_all_false" name={props.name} - onChange={props.handleChange} value={false} checked={!require_all} /> - <label htmlFor="citation_require_all_false">Show cases citing any of them</label> - <input type="radio" id="citation_require_all_true" name={props.name} - onChange={props.handleChange} value={true} checked={require_all} /> - <label htmlFor="citation_require_all_true">Show cases citing all of them</label> - </div> + const require_all = props.value === 'true'; + return ( + <div> + <label>If more than one citation is entered:</label> + <input + type="radio" + id="citation_require_all_false" + name={props.name} + onChange={props.handleChange} + value={false} + checked={!require_all} + /> + <label htmlFor="citation_require_all_false"> + Show cases citing any of them + </label> + <input + type="radio" + id="citation_require_all_true" + name={props.name} + onChange={props.handleChange} + value={true} + checked={require_all} + /> + <label htmlFor="citation_require_all_true"> + Show cases citing all of them + </label> + </div> + ); } module.exports = CitationRequireAllRadio; diff --git a/fec/fec/static/js/legal/filters/DateFilter.js b/fec/fec/static/js/legal/filters/DateFilter.js index 6433d7bfce..4352ac1f39 100644 --- a/fec/fec/static/js/legal/filters/DateFilter.js +++ b/fec/fec/static/js/legal/filters/DateFilter.js @@ -1,31 +1,50 @@ const React = require('react'); -const InputElement = require('react-input-mask') +const InputElement = require('react-input-mask'); function DateFilter(props) { function interceptHandleChange(e) { - if(new RegExp("[0-9][0-9]/[0-9][0-9]/[0-9][0-9][0-9][0-9]").test(e.target.value) || e.target.value === '') { + if ( + new RegExp('[0-9][0-9]/[0-9][0-9]/[0-9][0-9][0-9][0-9]').test( + e.target.value + ) || + e.target.value === '' + ) { props.instantQuery(e); } else { props.setQuery(e); } } - return <fieldset className="filter"> - <legend className="label">{props.label}</legend> - <div className="range range--date js-date-range"> - <div className="range__input range__input--min" data-filter="range"> - <label htmlFor={props.min_name}>Beginning</label> - <InputElement mask="99/99/9999" id={props.min_name} name={props.min_name} value={props.min_value || ''} - onChange={interceptHandleChange} placeholder="mm/dd/yyyy" /> + return ( + <fieldset className="filter"> + <legend className="label">{props.label}</legend> + <div className="range range--date js-date-range"> + <div className="range__input range__input--min" data-filter="range"> + <label htmlFor={props.min_name}>Beginning</label> + <InputElement + mask="99/99/9999" + id={props.min_name} + name={props.min_name} + value={props.min_value || ''} + onChange={interceptHandleChange} + placeholder="mm/dd/yyyy" + /> + </div> + <div className="range__hyphen">-</div> + <div className="range__input range__input--max" data-filter="range"> + <label htmlFor={props.max_name}>Ending</label> + <InputElement + mask="99/99/9999" + id={props.max_name} + name={props.max_name} + value={props.max_value || ''} + onChange={interceptHandleChange} + placeholder="mm/dd/yyyy" + /> + </div> </div> - <div className="range__hyphen">-</div> - <div className="range__input range__input--max" data-filter="range"> - <label htmlFor={props.max_name}>Ending</label> - <InputElement mask="99/99/9999" id={props.max_name} name={props.max_name} value={props.max_value || ''} - onChange={interceptHandleChange} placeholder="mm/dd/yyyy" /> - </div> - </div> - </fieldset> + </fieldset> + ); } module.exports = DateFilter; diff --git a/fec/fec/static/js/legal/filters/Dropdown.js b/fec/fec/static/js/legal/filters/Dropdown.js index 5aed0395cf..44a4f86e1d 100644 --- a/fec/fec/static/js/legal/filters/Dropdown.js +++ b/fec/fec/static/js/legal/filters/Dropdown.js @@ -6,12 +6,20 @@ function Dropdown(props) { props.handleChange({ target: { name: props.name, value: value } }); } - return <div className="filter"> - <label htmlFor={props.name} className="label">{props.label}</label> - <select id={props.name} name={props.name} onChange={handleDropdownChange}> - {props.options.map(option => <option key={option.value} value={option.value}>{option.text}</option>)} - </select> - </div> + return ( + <div className="filter"> + <label htmlFor={props.name} className="label"> + {props.label} + </label> + <select id={props.name} name={props.name} onChange={handleDropdownChange}> + {props.options.map(option => ( + <option key={option.value} value={option.value}> + {option.text} + </option> + ))} + </select> + </div> + ); } module.exports = Dropdown; diff --git a/fec/fec/static/js/legal/filters/TextFilter.js b/fec/fec/static/js/legal/filters/TextFilter.js index 8d9125512a..ce00712fe2 100644 --- a/fec/fec/static/js/legal/filters/TextFilter.js +++ b/fec/fec/static/js/legal/filters/TextFilter.js @@ -1,32 +1,48 @@ const React = require('react'); function TextFilter(props) { - function handleKeydown(e) { - if (e.keyCode === 13) { - props.getResults(); - } - } + if (e.keyCode === 13) { + props.getResults(); + } + } - return <div className="filter"> - <label className="label" htmlFor={props.name + "-filter"}>{props.label}</label> + return ( + <div className="filter"> + <label className="label" htmlFor={props.name + '-filter'}> + {props.label} + </label> <div className="combo combo--search--mini"> - <input id={props.name + "-filter"} type="text" name={props.name} className="combo__input" - value={props.value || ''} onChange={props.handleChange} onKeyDown={handleKeydown}/> - <button className="combo__button button--search button--standard" - onClick={props.getResults}> + <input + id={props.name + '-filter'} + type="text" + name={props.name} + className="combo__input" + value={props.value || ''} + onChange={props.handleChange} + onKeyDown={handleKeydown} + /> + <button + className="combo__button button--search button--standard" + onClick={props.getResults} + > <span className="u-visually-hidden">Search</span> </button> </div> - {props.keywordModal && + {props.keywordModal && ( <button className="button--keywords" aria-controls="keyword-modal" - data-a11y-dialog-show="keyword-modal">More keyword options</button> - } - {props.helpText && <span className="t-note t-sans search__example"> - {props.helpText}</span>} + data-a11y-dialog-show="keyword-modal" + > + More keyword options + </button> + )} + {props.helpText && ( + <span className="t-note t-sans search__example">{props.helpText}</span> + )} </div> + ); } module.exports = TextFilter; diff --git a/fec/fec/static/js/legal/requestorOptions.js b/fec/fec/static/js/legal/requestorOptions.js index 24cabab1be..50d58816be 100644 --- a/fec/fec/static/js/legal/requestorOptions.js +++ b/fec/fec/static/js/legal/requestorOptions.js @@ -1,17 +1,28 @@ -module.exports = [ {'value': '0', 'text': 'Any'}, - {'value': '1', 'text': 'Federal candidate/candidate committee/officeholder'}, - {'value': '2', 'text': 'Publicly funded candidates/committees'}, - {'value': '3', 'text': 'Party committee, national'}, - {'value': '4', 'text': 'Party committee, state or local'}, - {'value': '5', 'text': 'Nonconnected political committee'}, - {'value': '6', 'text': 'Separate segregated fund'}, - {'value': '7', 'text': 'Labor Organization'}, - {'value': '8', 'text': 'Trade Association'}, - {'value': '9', 'text': 'Membership Organization, Cooperative, Corporation W/O Capital Stock'}, - {'value': '10', 'text': 'Corporation (including LLCs electing corporate status)'}, - {'value': '11', 'text': 'Partnership (including LLCs electing partnership status)'}, - {'value': '12', 'text': 'Governmental entity'}, - {'value': '13', 'text': 'Research/Public Interest/Educational Institution'}, - {'value': '14', 'text': 'Law Firm'}, - {'value': '15', 'text': 'Individual'}, - {'value': '16', 'text': 'Other'}]; +module.exports = [ + { value: '0', text: 'Any' }, + { value: '1', text: 'Federal candidate/candidate committee/officeholder' }, + { value: '2', text: 'Publicly funded candidates/committees' }, + { value: '3', text: 'Party committee, national' }, + { value: '4', text: 'Party committee, state or local' }, + { value: '5', text: 'Nonconnected political committee' }, + { value: '6', text: 'Separate segregated fund' }, + { value: '7', text: 'Labor Organization' }, + { value: '8', text: 'Trade Association' }, + { + value: '9', + text: 'Membership Organization, Cooperative, Corporation W/O Capital Stock' + }, + { + value: '10', + text: 'Corporation (including LLCs electing corporate status)' + }, + { + value: '11', + text: 'Partnership (including LLCs electing partnership status)' + }, + { value: '12', text: 'Governmental entity' }, + { value: '13', text: 'Research/Public Interest/Educational Institution' }, + { value: '14', text: 'Law Firm' }, + { value: '15', text: 'Individual' }, + { value: '16', text: 'Other' } +]; diff --git a/fec/fec/static/js/modules/accessibility.js b/fec/fec/static/js/modules/accessibility.js index b65cc6e432..8ae5b9a1d4 100644 --- a/fec/fec/static/js/modules/accessibility.js +++ b/fec/fec/static/js/modules/accessibility.js @@ -11,15 +11,11 @@ var $ = require('jquery'); */ function removeTabindex($elm) { - $elm - .find('a, button, :input, [tabindex]') - .attr('tabindex', '-1'); + $elm.find('a, button, :input, [tabindex]').attr('tabindex', '-1'); } function restoreTabindex($elm) { - $elm - .find('a, button, :input, [tabindex]') - .attr('tabindex', '0'); + $elm.find('a, button, :input, [tabindex]').attr('tabindex', '0'); } module.exports = { diff --git a/fec/fec/static/js/modules/analytics.js b/fec/fec/static/js/modules/analytics.js index 3ce06f95b5..3c8862f9e7 100644 --- a/fec/fec/static/js/modules/analytics.js +++ b/fec/fec/static/js/modules/analytics.js @@ -18,10 +18,25 @@ function trackerExists() { */ function init() { if (!trackerExists()) { - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); + (function(i, s, o, g, r, a, m) { + i['GoogleAnalyticsObject'] = r; + (i[r] = + i[r] || + function() { + (i[r].q = i[r].q || []).push(arguments); + }), + (i[r].l = 1 * new Date()); + (a = s.createElement(o)), (m = s.getElementsByTagName(o)[0]); + a.async = 1; + a.src = g; + m.parentNode.insertBefore(a, m); + })( + window, + document, + 'script', + 'https://www.google-analytics.com/analytics.js', + 'ga' + ); } ga('create', 'UA-48605964-22', 'auto', 'notDAP'); @@ -30,10 +45,14 @@ function init() { } function pageView() { - if (typeof ga === 'undefined') { return; } + if (typeof ga === 'undefined') { + return; + } var path = document.location.pathname; if (document.location.search) { - var query = helpers.sanitizeQueryParams(URI.parseQuery(document.location.search)); + var query = helpers.sanitizeQueryParams( + URI.parseQuery(document.location.search) + ); path += '?' + sortQuery(query); } ga('notDAP.send', 'pageview', path); @@ -46,9 +65,11 @@ function sortQuery(query) { return [pair[0], _.isArray(pair[1]) ? pair[1] : [pair[1]]]; }) .reduce(function(memo, pair) { - return memo.concat(_.map(pair[1], function(value) { - return [pair[0], value]; - })); + return memo.concat( + _.map(pair[1], function(value) { + return [pair[0], value]; + }) + ); }, []) .sort() .map(function(pair) { diff --git a/fec/fec/static/js/modules/audit-category-sub-category.js b/fec/fec/static/js/modules/audit-category-sub-category.js index bf0765acf4..98cb3dc285 100644 --- a/fec/fec/static/js/modules/audit-category-sub-category.js +++ b/fec/fec/static/js/modules/audit-category-sub-category.js @@ -10,31 +10,44 @@ var _ = require('underscore'); var $ = require('jquery'); var helpers = require('./helpers'); -$("#primary_category_id").change(function(event) { - +$('#primary_category_id').change(function(event) { var $select = $('#sub_category_id'); - $.getJSON(helpers.buildUrl(['audit-category'],{'primary_category_id': $('#primary_category_id').val(), 'per_page': 100}), function(data){ - $select.html('<option selected value="all">All</option>') - $(".sub--filter--indent").css('opacity','.5').css({pointerEvents: "none"}) - - if (data.results[0]){ - $.each(data.results[0].sub_category_list, function(key, val){ - $select.append('<option value="' + val.sub_category_id + '">' + val.sub_category_name + '</option>'); - }); - $(".sub--filter--indent").css('opacity','1').css({pointerEvents: "auto"}); + $.getJSON( + helpers.buildUrl(['audit-category'], { + primary_category_id: $('#primary_category_id').val(), + per_page: 100 + }), + function(data) { + $select.html('<option selected value="all">All</option>'); + $('.sub--filter--indent') + .css('opacity', '.5') + .css({ pointerEvents: 'none' }); + + if (data.results[0]) { + $.each(data.results[0].sub_category_list, function(key, val) { + $select.append( + '<option value="' + + val.sub_category_id + + '">' + + val.sub_category_name + + '</option>' + ); + }); + $('.sub--filter--indent') + .css('opacity', '1') + .css({ pointerEvents: 'auto' }); + } } - }) -}) + ); +}); //for sub category filter-tag and results -function showSubCategory(){ - var sub_selected = $("#sub_category_id option:selected").text() - sub_selected == ("Choose a sub-category") || ($("#sub_category_id").val() == "all") - ? $('.tag__category.sub').css('visibility','hidden') - : $('.tag__category.sub').css('visibility','visible') - +function showSubCategory() { + var sub_selected = $('#sub_category_id option:selected').text(); + sub_selected == 'Choose a sub-category' || + $('#sub_category_id').val() == 'all' + ? $('.tag__category.sub').css('visibility', 'hidden') + : $('.tag__category.sub').css('visibility', 'visible'); } $(document).bind('ready ajaxComplete', '#sub_category_id', showSubCategory); - - diff --git a/fec/fec/static/js/modules/audit_tags.js b/fec/fec/static/js/modules/audit_tags.js index a29eeed33f..86636936df 100644 --- a/fec/fec/static/js/modules/audit_tags.js +++ b/fec/fec/static/js/modules/audit_tags.js @@ -7,35 +7,33 @@ var helpers = require('./helpers'); var AuditCategorySubCategory = require('./audit-category-sub-category'); -var $auditCategoryTags= require('../templates/audit_tags.hbs'); - - -$('.data-container__tags').prepend($auditCategoryTags) -$('.tag__category.sub').css('visibility','hidden') - - -$("#primary_category_id").change(function(){ - var current_category = $("#primary_category_id option:selected").text() - $('.tag__category.sub').css('visibility','hidden') - $('.tag__item.primary').contents()[0].nodeValue = "Findings and issue category: " + current_category - $("#primary_category_id").val() == 'all' - ? $('.tag__item.primary button').hide() - : $('.tag__item.primary button').show() +var $auditCategoryTags = require('../templates/audit_tags.hbs'); + +$('.data-container__tags').prepend($auditCategoryTags); +$('.tag__category.sub').css('visibility', 'hidden'); + +$('#primary_category_id').change(function() { + var current_category = $('#primary_category_id option:selected').text(); + $('.tag__category.sub').css('visibility', 'hidden'); + $('.tag__item.primary').contents()[0].nodeValue = + 'Findings and issue category: ' + current_category; + $('#primary_category_id').val() == 'all' + ? $('.tag__item.primary button').hide() + : $('.tag__item.primary button').show(); }); - -$("#sub_category_id").change(function() { - var current_sub = $("#sub_category_id option:selected").text() - $('.tag__item.sub').contents()[0].nodeValue = "Sub Category: "+ current_sub; -}) +$('#sub_category_id').change(function() { + var current_sub = $('#sub_category_id option:selected').text(); + $('.tag__item.sub').contents()[0].nodeValue = 'Sub Category: ' + current_sub; +}); $('.js-close_sub').click(function() { - $("#primary_category_id").trigger('change') - $("#sub_category_id").val("all") -}) + $('#primary_category_id').trigger('change'); + $('#sub_category_id').val('all'); +}); $('.js-close_primary').click(function() { - $("#primary_category_id").val("all") - $("#primary_category_id").trigger('change') - $('.tag__item.primary button').hide() -}) + $('#primary_category_id').val('all'); + $('#primary_category_id').trigger('change'); + $('.tag__item.primary button').hide(); +}); diff --git a/fec/fec/static/js/modules/calendar-helpers.js b/fec/fec/static/js/modules/calendar-helpers.js index 9082de3210..5cf565f31e 100644 --- a/fec/fec/static/js/modules/calendar-helpers.js +++ b/fec/fec/static/js/modules/calendar-helpers.js @@ -10,7 +10,13 @@ function getGoogleUrl(event) { dates = event.start.format(fmt) + '/' + event.end.format(fmt); } else { fmt = 'YYYYMMDD'; - dates = event.start.format(fmt) + '/' + event.start.clone().add(1, 'day').format(fmt); + dates = + event.start.format(fmt) + + '/' + + event.start + .clone() + .add(1, 'day') + .format(fmt); } return URI('https://calendar.google.com/calendar/render') .addQuery({ @@ -57,14 +63,22 @@ function checkStartTime(event) { function mapCategoryDescription(category) { // matches the category parameter from calendar date API var tooltipContent = { - 'Reporting Deadlines': 'Throughout the year, filers submit regularly scheduled reports about their campaign finance activity. These reporting requirements are outlined in Title 11 of the Code of Federal Regulations (CFR) and vary, depending on the type of filer.', - 'Election Dates': 'Federal elections. These include primary, general and special elections as well as caucuses and conventions.', - 'EC Periods': 'Electioneering communications are any broadcast, cable or satellite communication that:<ul class="list--bulleted"><li>Refer to a clearly identified candidate for federal office,</li><li>Are publicly distributed within certain time periods before an election, and</li><li>Are targeted to the relevant electorate. 11 CFR 100.29.</li></ul>', - 'IE Periods': 'Independent expenditures are expenditures for a communication: <ul class="list--bulleted"><li>That expressly advocate the election or defeat of a clearly identified candidate and</li><li>That are not made in cooperation, consultation or concert with, or at the request or suggestion of, any candidate, or his or her authorized committees or agents, or a political party committee or its agents. 11 CFR 100.16.</li></ul>', - 'Executive Sessions': 'Executive sessions are regular, closed meetings during which the Commission discusses pending enforcement actions, litigation and other matters that — by law — must be kept confidential.', - 'Open Meetings': 'Open meetings are regular, public meetings during which the Commission adopts new regulations, issues advisory opinions, approves audit reports of political committees and takes other actions to administer the campaign finance law.', - 'Roundtables': 'Roundtables are training opportunities offered to FEC filers and those interested in learning about campaign finance law. These voluntary, online workshops focus on specific compliance topics. Register to access online materials and technical information.', - 'AOs and Rules': 'The Commission follows these deadlines when issuing new guidance and rules. Advisory Opinions are official Commission responses about how to apply campaign finance regulations to specific, factual situations. The Commission writes rules and regulations and publishes them in the Federal Register.' + 'Reporting Deadlines': + 'Throughout the year, filers submit regularly scheduled reports about their campaign finance activity. These reporting requirements are outlined in Title 11 of the Code of Federal Regulations (CFR) and vary, depending on the type of filer.', + 'Election Dates': + 'Federal elections. These include primary, general and special elections as well as caucuses and conventions.', + 'EC Periods': + 'Electioneering communications are any broadcast, cable or satellite communication that:<ul class="list--bulleted"><li>Refer to a clearly identified candidate for federal office,</li><li>Are publicly distributed within certain time periods before an election, and</li><li>Are targeted to the relevant electorate. 11 CFR 100.29.</li></ul>', + 'IE Periods': + 'Independent expenditures are expenditures for a communication: <ul class="list--bulleted"><li>That expressly advocate the election or defeat of a clearly identified candidate and</li><li>That are not made in cooperation, consultation or concert with, or at the request or suggestion of, any candidate, or his or her authorized committees or agents, or a political party committee or its agents. 11 CFR 100.16.</li></ul>', + 'Executive Sessions': + 'Executive sessions are regular, closed meetings during which the Commission discusses pending enforcement actions, litigation and other matters that — by law — must be kept confidential.', + 'Open Meetings': + 'Open meetings are regular, public meetings during which the Commission adopts new regulations, issues advisory opinions, approves audit reports of political committees and takes other actions to administer the campaign finance law.', + Roundtables: + 'Roundtables are training opportunities offered to FEC filers and those interested in learning about campaign finance law. These voluntary, online workshops focus on specific compliance topics. Register to access online materials and technical information.', + 'AOs and Rules': + 'The Commission follows these deadlines when issuing new guidance and rules. Advisory Opinions are official Commission responses about how to apply campaign finance regulations to specific, factual situations. The Commission writes rules and regulations and publishes them in the Federal Register.' }; return tooltipContent[category]; diff --git a/fec/fec/static/js/modules/calendar-list-view.js b/fec/fec/static/js/modules/calendar-list-view.js index 1e06a7714f..1d6605f3a2 100644 --- a/fec/fec/static/js/modules/calendar-list-view.js +++ b/fec/fec/static/js/modules/calendar-list-view.js @@ -27,14 +27,18 @@ var categories = { Other: ['other'] }; -var categoriesInverse = _.reduce(_.pairs(categories), function(memo, pair) { - var key = pair[0]; - var values = pair[1]; - _.each(values, function(value) { - memo[value] = key; - }); - return memo; -}, {}); +var categoriesInverse = _.reduce( + _.pairs(categories), + function(memo, pair) { + var key = pair[0]; + var values = pair[1]; + _.each(values, function(value) { + memo[value] = key; + }); + return memo; + }, + {} +); var categoryGroups = function(events, start, end) { return _.chain(events) @@ -43,7 +47,9 @@ var categoryGroups = function(events, start, end) { }) .sortBy('start') .groupBy(function(event) { - var category = event.category ? event.category.split(/[ -]+/)[0].toLowerCase() : null; + var category = event.category + ? event.category.split(/[ -]+/)[0].toLowerCase() + : null; return categoriesInverse[category]; }) .map(function(values, key) { @@ -82,7 +88,6 @@ var chronologicalGroups = function(events, start, end) { return events; }; - var ListView = View.extend({ setDate: function(date) { var intervalUnit = this.options.duration.intervalUnit || this.intervalUnit; @@ -90,18 +95,20 @@ var ListView = View.extend({ }, renderEvents: function(events) { - var groups = this.options.categories ? - categoryGroups(events, this.start, this.end) : - chronologicalGroups(events, this.start, this.end); + var groups = this.options.categories + ? categoryGroups(events, this.start, this.end) + : chronologicalGroups(events, this.start, this.end); var settings = { duration: this.options.duration.intervalUnit, sortBy: this.options.sortBy }; - this.el.html(eventTemplate({groups: groups, settings: settings})); - this.dropdowns = $(this.el.html).find('.dropdown').map(function(idx, elm) { - return new dropdown.Dropdown($(elm), {checkboxes: false}); - }); + this.el.html(eventTemplate({ groups: groups, settings: settings })); + this.dropdowns = $(this.el.html) + .find('.dropdown') + .map(function(idx, elm) { + return new dropdown.Dropdown($(elm), { checkboxes: false }); + }); }, unrenderEvents: function() { diff --git a/fec/fec/static/js/modules/calendar-tooltip.js b/fec/fec/static/js/modules/calendar-tooltip.js index 6e9804d811..eec674fa12 100644 --- a/fec/fec/static/js/modules/calendar-tooltip.js +++ b/fec/fec/static/js/modules/calendar-tooltip.js @@ -10,7 +10,9 @@ function CalendarTooltip(content, $container) { this.$container = $container; this.$close = this.$content.find('.js-close'); this.$dropdown = this.$content.find('.dropdown'); - this.exportDropdown = new dropdown.Dropdown(this.$dropdown, {checkboxes: false}); + this.exportDropdown = new dropdown.Dropdown(this.$dropdown, { + checkboxes: false + }); this.events = new listeners.Listeners(); this.events.on(this.$close, 'click', this.close.bind(this)); @@ -21,7 +23,10 @@ function CalendarTooltip(content, $container) { CalendarTooltip.prototype.handleClickAway = function(e) { var $target = $(e.target); - if (!this.$content.has($target).length && !this.$container.has($target).length) { + if ( + !this.$content.has($target).length && + !this.$container.has($target).length + ) { this.close(); } }; @@ -34,4 +39,4 @@ CalendarTooltip.prototype.close = function() { this.events.clear(); }; -module.exports = {CalendarTooltip: CalendarTooltip}; +module.exports = { CalendarTooltip: CalendarTooltip }; diff --git a/fec/fec/static/js/modules/calendar.js b/fec/fec/static/js/modules/calendar.js index e3ffe6dd0e..83357fa7e4 100644 --- a/fec/fec/static/js/modules/calendar.js +++ b/fec/fec/static/js/modules/calendar.js @@ -32,10 +32,12 @@ var Grid = FC.Grid; // Globally override event sorting to order all-day events last // TODO: Convince fullcalendar.io support this behavior without monkey-patching Grid.prototype.compareEventSegs = function(seg1, seg2) { - return seg1.event.allDay - seg2.event.allDay || // put all-day events last (booleans cast to 0/1) + return ( + seg1.event.allDay - seg2.event.allDay || // put all-day events last (booleans cast to 0/1) seg1.eventStartMS - seg2.eventStartMS || // tie? earlier events go first seg2.eventDurationMS - seg1.eventDurationMS || // tie? longer events go first - FC.compareByFieldSpecs(seg1.event, seg2.event, this.view.eventOrderSpecs); + FC.compareByFieldSpecs(seg1.event, seg2.event, this.view.eventOrderSpecs) + ); }; function Calendar(opts) { @@ -60,7 +62,11 @@ function Calendar(opts) { this.$calendar.on('click', '.js-toggle-view', this.toggleListView.bind(this)); - this.$calendar.on('keypress', '.fc-event, .fc-more, .fc-close', this.simulateClick.bind(this)); + this.$calendar.on( + 'keypress', + '.fc-event, .fc-more, .fc-close', + this.simulateClick.bind(this) + ); this.$calendar.on('click', '.fc-more', this.managePopoverControl.bind(this)); this.filterPanel.$form.on('change', this.filter.bind(this)); @@ -92,7 +98,7 @@ Calendar.prototype.defaultOpts = function() { buttonIcons: false, buttonText: { today: 'This Month', - week: 'Week', + week: 'Week' }, contentHeight: 'auto', dayRender: this.handleDayRender.bind(this), @@ -117,14 +123,14 @@ Calendar.prototype.defaultOpts = function() { type: 'list', categories: true, sortBy: 'category', - duration: {months: 1, intervalUnit: 'month'} + duration: { months: 1, intervalUnit: 'month' } }, monthTime: { type: 'list', buttonText: 'List', sortBy: 'time', - duration: {months: 1, intervalUnit: 'month'} - }, + duration: { months: 1, intervalUnit: 'month' } + } } }, sourceOpts: { @@ -140,10 +146,13 @@ Calendar.prototype.filter = function() { if (_.isEqual(params, this.params)) { return; } - var url = this.url.clone().addQuery(params || {}).toString(); + var url = this.url + .clone() + .addQuery(params || {}) + .toString(); urls.pushQuery(this.filterSet.serialize(), this.filterSet.fields); this.$calendar.fullCalendar('removeEventSource', this.sources); - this.sources = $.extend({}, this.opts.sourceOpts, {url: url}); + this.sources = $.extend({}, this.opts.sourceOpts, { url: url }); this.$calendar.fullCalendar('addEventSource', this.sources); this.updateLinks(params); this.params = params; @@ -153,7 +162,9 @@ Calendar.prototype.success = function(response) { var self = this; setTimeout(function() { - $('.is-loading').removeClass('is-loading').addClass('is-successful'); + $('.is-loading') + .removeClass('is-loading') + .addClass('is-successful'); }, helpers.LOADING_DELAY); setTimeout(function() { @@ -177,7 +188,10 @@ Calendar.prototype.success = function(response) { }; _.extend(processed, { google: calendarHelpers.getGoogleUrl(processed), - download: self.exportUrl.clone().addQuery({event_id: event.event_id}).toString() + download: self.exportUrl + .clone() + .addQuery({ event_id: event.event_id }) + .toString() }); return processed; }); @@ -187,11 +201,20 @@ Calendar.prototype.updateLinks = function(params) { var url = this.exportUrl.clone().addQuery(params || {}); var urls = { ics: url.toString(), - csv: url.clone().addQuery({renderer: 'csv'}).toString(), + csv: url + .clone() + .addQuery({ renderer: 'csv' }) + .toString(), // Note: The cid parameter silently rejects https links; use http and allow // the backend to redirect to https - google: 'https://calendar.google.com/calendar/render?cid=' + - encodeURIComponent(url.clone().protocol('http').toString()), + google: + 'https://calendar.google.com/calendar/render?cid=' + + encodeURIComponent( + url + .clone() + .protocol('http') + .toString() + ), calendar: url.protocol('webcal').toString() }; this.$download.html(templates.download(urls)); @@ -205,18 +228,30 @@ Calendar.prototype.updateLinks = function(params) { this.subscribeButton.destroy(); } - this.downloadButton = new dropdown.Dropdown(this.$download, {checkboxes: false}); - this.subscribeButton = new dropdown.Dropdown(this.$subscribe, {checkboxes: false}); + this.downloadButton = new dropdown.Dropdown(this.$download, { + checkboxes: false + }); + this.subscribeButton = new dropdown.Dropdown(this.$subscribe, { + checkboxes: false + }); }; Calendar.prototype.styleButtons = function() { var baseClasses = 'button'; this.$calendar.find('.fc-button').addClass(baseClasses); this.$calendar.find('.fc-today-button').addClass('button--alt'); - this.$calendar.find('.fc-next-button').addClass('button--next button--standard'); - this.$calendar.find('.fc-prev-button').addClass('button--previous button--standard'); - this.$calendar.find('.fc-right .fc-button-group').addClass('toggles--buttons'); - this.$calendar.find('.fc-monthTime-button').addClass('button--list button--alt'); + this.$calendar + .find('.fc-next-button') + .addClass('button--next button--standard'); + this.$calendar + .find('.fc-prev-button') + .addClass('button--previous button--standard'); + this.$calendar + .find('.fc-right .fc-button-group') + .addClass('toggles--buttons'); + this.$calendar + .find('.fc-monthTime-button') + .addClass('button--list button--alt'); this.$calendar.find('.fc-month-button').addClass('button--grid button--alt'); }; @@ -228,7 +263,9 @@ Calendar.prototype.handleRender = function(view) { this.$listToggles.remove(); this.$listToggles = null; } - this.$calendar.find('.fc-more').attr({'tabindex': '0', 'aria-describedby': this.popoverId}); + this.$calendar + .find('.fc-more') + .attr({ tabindex: '0', 'aria-describedby': this.popoverId }); this.$head.find('.js-calendar-title').html(view.title); }; @@ -245,11 +282,14 @@ Calendar.prototype.manageListToggles = function(view) { }; Calendar.prototype.handleEventRender = function(event, element) { - var eventLabel = event.title + ' ' + + var eventLabel = + event.title + + ' ' + event.start.format('dddd MMMM D, YYYY') + - '. Category: ' + event.category; + '. Category: ' + + event.category; element.attr({ - 'tabindex': '0', + tabindex: '0', 'aria-describedby': this.detailsId, 'aria-label': eventLabel }); @@ -266,7 +306,7 @@ Calendar.prototype.handleEventClick = function(calEvent, jsEvent, view) { if (!$target.closest('.tooltip').length) { var $eventContainer = $target.closest('.fc-event'); var tooltip = new calendarTooltip.CalendarTooltip( - templates.details(_.extend({}, calEvent, {detailsId: this.detailsId})), + templates.details(_.extend({}, calEvent, { detailsId: this.detailsId })), $eventContainer ); @@ -285,7 +325,8 @@ Calendar.prototype.managePopoverControl = function(e) { var $target = $(e.target); var $popover = this.$calendar.find('.fc-popover'); $popover.attr('id', this.popoverId).attr('role', 'tooltip'); - $popover.find('.fc-close') + $popover + .find('.fc-close') .attr('tabindex', '0') .focus() .on('click', function() { @@ -293,4 +334,4 @@ Calendar.prototype.managePopoverControl = function(e) { }); }; -module.exports = {Calendar: Calendar}; +module.exports = { Calendar: Calendar }; diff --git a/fec/fec/static/js/modules/column-helpers.js b/fec/fec/static/js/modules/column-helpers.js index 3e58be4747..45b59a41ce 100644 --- a/fec/fec/static/js/modules/column-helpers.js +++ b/fec/fec/static/js/modules/column-helpers.js @@ -5,11 +5,11 @@ var _ = require('underscore'); var helpers = require('./helpers'); var sizeInfo = { - 0: {limits: [0, 200], label: '$200 and under'}, - 200: {limits: [200.01, 499.99], label: '$200.01—$499'}, - 500: {limits: [500, 999.99], label: '$500—$999'}, - 1000: {limits: [1000, 1999.99], label: '$1,000—$1,999'}, - 2000: {limits: [2000, null], label: '$2,000 and over'}, + 0: { limits: [0, 200], label: '$200 and under' }, + 200: { limits: [200.01, 499.99], label: '$200.01—$499' }, + 500: { limits: [500, 999.99], label: '$500—$999' }, + 1000: { limits: [1000, 1999.99], label: '$1,000—$1,999' }, + 2000: { limits: [2000, null], label: '$2,000 and over' } }; function getSizeParams(size) { @@ -33,44 +33,59 @@ function getColumns(columns, keys) { function formattedColumn(formatter, defaultOpts) { defaultOpts = defaultOpts || {}; return function(opts) { - return _.extend({}, defaultOpts, { - render: function(data, type, row, meta) { - return formatter(data, type, row, meta); - } - }, opts); + return _.extend( + {}, + defaultOpts, + { + render: function(data, type, row, meta) { + return formatter(data, type, row, meta); + } + }, + opts + ); }; } function barColumn(formatter) { - formatter = formatter || function(value) { return value; }; + formatter = + formatter || + function(value) { + return value; + }; return function(opts) { - return _.extend({ - orderSequence: ['desc', 'asc'], - render: function(data, type, row, meta) { - var span = document.createElement('div'); - span.textContent = formatter(_.max([data, 0])); - span.setAttribute('data-value', data || 0); - span.setAttribute('data-row', meta.row); - return span.outerHTML; - } - }, opts); + return _.extend( + { + orderSequence: ['desc', 'asc'], + render: function(data, type, row, meta) { + var span = document.createElement('div'); + span.textContent = formatter(_.max([data, 0])); + span.setAttribute('data-value', data || 0); + span.setAttribute('data-row', meta.row); + return span.outerHTML; + } + }, + opts + ); }; } function urlColumn(attr, opts) { - return _.extend({ - render: function(data, type, row, meta) { - if (row[attr]) { - var anchor = document.createElement('a'); - anchor.textContent = data; - anchor.setAttribute('href', row[attr]); - anchor.setAttribute('target', '_blank'); - return anchor.outerHTML; - } else { - return data; + return _.extend( + { + render: function(data, type, row, meta) { + if (row[attr]) { + var anchor = document.createElement('a'); + anchor.textContent = data; + anchor.setAttribute('href', row[attr]); + anchor.setAttribute('target', '_blank'); + return anchor.outerHTML; + } else { + return data; + } } - } - }, opts); + }, + opts + ); } function buildEntityLink(data, url, category, opts) { @@ -117,15 +132,21 @@ function buildTotalLink(path, getParams) { if (path.indexOf('receipts') > -1 || path.indexOf('disbursements') > -1) { includeTransactionPeriod = true; } - var uri = helpers.buildAppUrl(path, _.extend( - {committee_id: row.committee_id}, - buildAggregateUrl(_.extend({}, row, params).cycle, includeTransactionPeriod), - params - )); + var uri = helpers.buildAppUrl( + path, + _.extend( + { committee_id: row.committee_id }, + buildAggregateUrl( + _.extend({}, row, params).cycle, + includeTransactionPeriod + ), + params + ) + ); link.setAttribute('href', uri); span.appendChild(link); // Temporarily disable "other" state aggs - if (params.contributor_state == 'OT'){ + if (params.contributor_state == 'OT') { span.textContent = helpers.currency(data); } } else { @@ -136,21 +157,40 @@ function buildTotalLink(path, getParams) { } function makeCommitteeColumn(opts, context, factory) { - return _.extend({}, { - orderSequence: ['desc', 'asc'], - className: 'column--number', - render: buildTotalLink(['receipts', 'individual-contributions'], function(data, type, row, meta) { - row.cycle = context.election.cycle; - var column = meta.settings.aoColumns[meta.col].data; - return _.extend({ - committee_id: (context.candidates[row.candidate_id] || {}).committee_ids, - two_year_transaction_period: row.cycle, - }, factory(data, type, row, meta, column)); - }) - }, opts); + return _.extend( + {}, + { + orderSequence: ['desc', 'asc'], + className: 'column--number', + render: buildTotalLink(['receipts', 'individual-contributions'], function( + data, + type, + row, + meta + ) { + row.cycle = context.election.cycle; + var column = meta.settings.aoColumns[meta.col].data; + return _.extend( + { + committee_id: (context.candidates[row.candidate_id] || {}) + .committee_ids, + two_year_transaction_period: row.cycle + }, + factory(data, type, row, meta, column) + ); + }) + }, + opts + ); } -var makeSizeColumn = _.partial(makeCommitteeColumn, _, _, function(data, type, row, meta, column) { +var makeSizeColumn = _.partial(makeCommitteeColumn, _, _, function( + data, + type, + row, + meta, + column +) { return getSizeParams(column); }); @@ -168,28 +208,30 @@ function sizeColumns(context) { ); } }, - makeSizeColumn({data: '0'}, context), - makeSizeColumn({data: '200'}, context), - makeSizeColumn({data: '500'}, context), - makeSizeColumn({data: '1000'}, context), - makeSizeColumn({data: '2000'}, context) + makeSizeColumn({ data: '0' }, context), + makeSizeColumn({ data: '200' }, context), + makeSizeColumn({ data: '500' }, context), + makeSizeColumn({ data: '1000' }, context), + makeSizeColumn({ data: '2000' }, context) ]; } function stateColumns(results, context) { - var stateColumn = {'data': 'state'}; + var stateColumn = { data: 'state' }; var columns = _.map(results, function(result) { - return makeCommitteeColumn( - {data: result.candidate_id}, - context, - function(data, type, row, meta, column) { - return { - contributor_state: row.state, - committee_id: (context.candidates[column] || {}).committee_ids, - is_individual: 'true' - }; - } - ); + return makeCommitteeColumn({ data: result.candidate_id }, context, function( + data, + type, + row, + meta, + column + ) { + return { + contributor_state: row.state, + committee_id: (context.candidates[column] || {}).committee_ids, + is_individual: 'true' + }; + }); }); return [stateColumn].concat(columns); diff --git a/fec/fec/static/js/modules/columns.js b/fec/fec/static/js/modules/columns.js index 303ed49a92..122913b060 100644 --- a/fec/fec/static/js/modules/columns.js +++ b/fec/fec/static/js/modules/columns.js @@ -10,8 +10,12 @@ var moment = require('moment'); var reportType = require('../templates/reports/reportType.hbs'); -var dateColumn = columnHelpers.formattedColumn(helpers.datetime, {orderSequence: ['desc', 'asc']}); -var currencyColumn = columnHelpers.formattedColumn(helpers.currency, {orderSequence: ['desc', 'asc']}); +var dateColumn = columnHelpers.formattedColumn(helpers.datetime, { + orderSequence: ['desc', 'asc'] +}); +var currencyColumn = columnHelpers.formattedColumn(helpers.currency, { + orderSequence: ['desc', 'asc'] +}); var barCurrencyColumn = columnHelpers.barColumn(helpers.currency); var supportOpposeColumn = { @@ -32,10 +36,12 @@ var versionColumn = { } var version = helpers.amendmentVersion(data); if (version === 'Version unknown') { - return '<i class="icon-blank"></i>Version unknown<br>' + - '<i class="icon-blank"></i>' + row.fec_file_id; - } - else { + return ( + '<i class="icon-blank"></i>Version unknown<br>' + + '<i class="icon-blank"></i>' + + row.fec_file_id + ); + } else { if (row.fec_file_id !== null) { version = version + '<br><i class="icon-blank"></i>' + row.fec_file_id; } @@ -60,7 +66,9 @@ var receiptDateColumn = { var parsed; if (meta.settings.oInit.path.indexOf('efile') >= 0) { parsed = moment(row.receipt_date, 'YYYY-MM-DDTHH:mm:ss'); - return parsed.isValid() ? parsed.format('MM/DD/YYYY, h:mma') : 'Invalid date'; + return parsed.isValid() + ? parsed.format('MM/DD/YYYY, h:mma') + : 'Invalid date'; } else { parsed = moment(row.receipt_date, 'YYYY-MM-DDTHH:mm:ss'); return parsed.isValid() ? parsed.format('MM/DD/YYYY') : 'Invalid date'; @@ -85,14 +93,21 @@ var pagesColumn = { return number; } }; - var pages = shorten(row.ending_image_number) - shorten(row.beginning_image_number) + 1; + var pages = + shorten(row.ending_image_number) - + shorten(row.beginning_image_number) + + 1; return pages.toLocaleString(); } }; var candidateColumn = columnHelpers.formattedColumn(function(data, type, row) { if (row) { - return columnHelpers.buildEntityLink(row.candidate_name, helpers.buildAppUrl(['candidate', row.candidate_id]), 'candidate'); + return columnHelpers.buildEntityLink( + row.candidate_name, + helpers.buildAppUrl(['candidate', row.candidate_id]), + 'candidate' + ); } else { return ''; } @@ -100,7 +115,11 @@ var candidateColumn = columnHelpers.formattedColumn(function(data, type, row) { var committeeColumn = columnHelpers.formattedColumn(function(data, type, row) { if (row) { - return columnHelpers.buildEntityLink(row.committee_name, helpers.buildAppUrl(['committee', row.committee_id]), 'committee'); + return columnHelpers.buildEntityLink( + row.committee_name, + helpers.buildAppUrl(['committee', row.committee_id]), + 'committee' + ); } else { return ''; } @@ -111,7 +130,8 @@ var renderCandidateColumn = function(data, type, row, meta) { return columnHelpers.buildEntityLink( data, helpers.buildAppUrl(['candidate', row.candidate_id]), - 'candidate'); + 'candidate' + ); } else { return ''; } @@ -122,15 +142,16 @@ var renderCommitteeColumn = function(data, type, row, meta) { return columnHelpers.buildEntityLink( data, helpers.buildAppUrl(['committee', row.committee_id]), - 'committee'); + 'committee' + ); } else { return ''; } }; var candidates = [ - {data: 'name', className: 'all', render: renderCandidateColumn}, - {data: 'office_full', className: 'min-tablet hide-panel-tablet'}, + { data: 'name', className: 'all', render: renderCandidateColumn }, + { data: 'office_full', className: 'min-tablet hide-panel-tablet' }, { data: 'election_years', className: 'min-tablet hide-panel', @@ -138,20 +159,33 @@ var candidates = [ return tables.yearRange(_.first(data), _.last(data)); } }, - {data: 'party_full', className: 'min-tablet hide-panel'}, - {data: 'state', className: 'min-desktop hide-panel column--state'}, - {data: 'district', className: 'min-desktop hide-panel column--small'}, - {data: 'first_file_date', orderable: true, className: 'min-desktop hide-panel column--small'}, + { data: 'party_full', className: 'min-tablet hide-panel' }, + { data: 'state', className: 'min-desktop hide-panel column--state' }, + { data: 'district', className: 'min-desktop hide-panel column--small' }, + { + data: 'first_file_date', + orderable: true, + className: 'min-desktop hide-panel column--small' + }, modalTriggerColumn ]; var candidateOffice = { - name: {data: 'name', className: 'all', render: renderCandidateColumn}, - party: {data: 'party_full', className: 'min-desktop'}, - state: {data: 'state', className: 'min-tablet column--state hide-panel'}, - district: {data: 'district', className: 'min-desktop column--small hide-panel'}, - receipts: currencyColumn({data: 'receipts', className: 'min-tablet hide-panel column--number'}), - disbursements: currencyColumn({data: 'disbursements', className: 'min-tablet hide-panel column--number'}), + name: { data: 'name', className: 'all', render: renderCandidateColumn }, + party: { data: 'party_full', className: 'min-desktop' }, + state: { data: 'state', className: 'min-tablet column--state hide-panel' }, + district: { + data: 'district', + className: 'min-desktop column--small hide-panel' + }, + receipts: currencyColumn({ + data: 'receipts', + className: 'min-tablet hide-panel column--number' + }), + disbursements: currencyColumn({ + data: 'disbursements', + className: 'min-tablet hide-panel column--number' + }), trigger: modalTriggerColumn }; @@ -163,17 +197,25 @@ var committees = [ if (data) { return columnHelpers.buildEntityLink( data, - helpers.buildAppUrl(['committee', row.committee_id], tables.getCycle(row.cycles, meta)), - 'committee'); + helpers.buildAppUrl( + ['committee', row.committee_id], + tables.getCycle(row.cycles, meta) + ), + 'committee' + ); } else { return ''; } } }, - {data: 'treasurer_name', className: 'min-desktop hide-panel'}, - {data: 'committee_type_full', className: 'min-tablet hide-panel'}, - {data: 'designation_full', className: 'min-tablet hide-panel'}, - {data: 'first_file_date', orderable: true, className: 'min-desktop hide-panel column--small'}, + { data: 'treasurer_name', className: 'min-desktop hide-panel' }, + { data: 'committee_type_full', className: 'min-tablet hide-panel' }, + { data: 'designation_full', className: 'min-tablet hide-panel' }, + { + data: 'first_file_date', + orderable: true, + className: 'min-desktop hide-panel column--small' + }, modalTriggerColumn ]; @@ -182,17 +224,25 @@ var communicationCosts = [ data: 'committee_name', orderable: false, className: 'all', - render: renderCommitteeColumn, + render: renderCommitteeColumn }, - _.extend({}, supportOpposeColumn, {className: 'min-tablet hide-panel-tablet'}), + _.extend({}, supportOpposeColumn, { + className: 'min-tablet hide-panel-tablet' + }), { data: 'candidate_name', orderable: false, className: 'min-tablet hide-panel-tablet', render: renderCandidateColumn }, - currencyColumn({data: 'transaction_amount', className: 'min-tablet hide-panel column--number'}), - dateColumn({data: 'transaction_date', className: 'min-tablet hide-panel column--small'}), + currencyColumn({ + data: 'transaction_amount', + className: 'min-tablet hide-panel column--number' + }), + dateColumn({ + data: 'transaction_date', + className: 'min-tablet hide-panel column--small' + }), modalTriggerColumn ]; @@ -230,10 +280,24 @@ var disbursements = [ } } }, - {data: 'recipient_state', orderable: false, className: 'min-desktop column--state hide-panel'}, - {data: 'disbursement_description', className: 'min-desktop hide-panel', orderable: false}, - dateColumn({data: 'disbursement_date', className: 'min-tablet hide-panel column--small'}), - currencyColumn({data: 'disbursement_amount', className: 'min-tablet hide-panel column--number'}), + { + data: 'recipient_state', + orderable: false, + className: 'min-desktop column--state hide-panel' + }, + { + data: 'disbursement_description', + className: 'min-desktop hide-panel', + orderable: false + }, + dateColumn({ + data: 'disbursement_date', + className: 'min-tablet hide-panel column--small' + }), + currencyColumn({ + data: 'disbursement_amount', + className: 'min-tablet hide-panel column--number' + }), modalTriggerColumn ]; @@ -252,11 +316,20 @@ var electioneeringCommunications = [ }, { data: 'number_of_candidates', - className: 'min-desktop hide-panel column--small column--number', + className: 'min-desktop hide-panel column--small column--number' }, - currencyColumn({data: 'calculated_candidate_share', className: 'min-desktop hide-panel column--number'}), - dateColumn({data: 'disbursement_date', className: 'min-tablet hide-panel column--small'}), - currencyColumn({data: 'disbursement_amount', className: 'min-tablet hide-panel column--number'}), + currencyColumn({ + data: 'calculated_candidate_share', + className: 'min-desktop hide-panel column--number' + }), + dateColumn({ + data: 'disbursement_date', + className: 'min-tablet hide-panel column--small' + }), + currencyColumn({ + data: 'disbursement_amount', + className: 'min-tablet hide-panel column--number' + }), modalTriggerColumn ]; @@ -282,7 +355,7 @@ var filings = { } else { return ''; } - }, + } }, pdf_url: columnHelpers.urlColumn('pdf_url', { // This is just used by the committee pages because those tables @@ -296,7 +369,9 @@ var filings = { className: 'all column--doc-download', orderable: false, render: function(data, type, row) { - var doc_description = row.document_description ? row.document_description : row.form_type; + var doc_description = row.document_description + ? row.document_description + : row.form_type; var amendment_version = helpers.amendmentVersionDescription(row); var pdf_url = row.pdf_url ? row.pdf_url : null; var csv_url = row.csv_url ? row.csv_url : null; @@ -334,11 +409,28 @@ var filings = { data: 'beginning_image_number', orderable: false }, - coverage_start_date: dateColumn({data: 'coverage_start_date', className: 'min-tablet hide-panel column--small', orderable: false}), - coverage_end_date: dateColumn({data: 'coverage_end_date', className: 'min-tablet hide-panel column--small', orderable: false}), - total_receipts: currencyColumn({data: 'total_receipts', className: 'min-desktop hide-panel column--number'}), - total_disbursements: currencyColumn({data: 'total_disbursements', className: 'min-desktop hide-panel column--number'}), - total_independent_expenditures: currencyColumn({data: 'total_independent_expenditures', className: 'min-desktop hide-panel column--number'}), + coverage_start_date: dateColumn({ + data: 'coverage_start_date', + className: 'min-tablet hide-panel column--small', + orderable: false + }), + coverage_end_date: dateColumn({ + data: 'coverage_end_date', + className: 'min-tablet hide-panel column--small', + orderable: false + }), + total_receipts: currencyColumn({ + data: 'total_receipts', + className: 'min-desktop hide-panel column--number' + }), + total_disbursements: currencyColumn({ + data: 'total_disbursements', + className: 'min-desktop hide-panel column--number' + }), + total_independent_expenditures: currencyColumn({ + data: 'total_independent_expenditures', + className: 'min-desktop hide-panel column--number' + }), modal_trigger: { className: 'all column--trigger hide-efiling', orderable: false, @@ -369,7 +461,9 @@ var independentExpenditures = [ } } }, - _.extend({}, supportOpposeColumn, {className: 'min-tablet hide-panel-tablet'}), + _.extend({}, supportOpposeColumn, { + className: 'min-tablet hide-panel-tablet' + }), { data: 'candidate_name', orderable: false, @@ -378,7 +472,10 @@ var independentExpenditures = [ if (row.candidate_id) { return columnHelpers.buildEntityLink( data, - helpers.buildAppUrl(['candidate', row.candidate_id], tables.getCycle(row, meta)), + helpers.buildAppUrl( + ['candidate', row.candidate_id], + tables.getCycle(row, meta) + ), 'candidate' ); } else { @@ -386,14 +483,24 @@ var independentExpenditures = [ } } }, - columnHelpers.urlColumn('pdf_url', {data: 'expenditure_description', className: 'min-desktop hide-panel', orderable: false}), + columnHelpers.urlColumn('pdf_url', { + data: 'expenditure_description', + className: 'min-desktop hide-panel', + orderable: false + }), { data: 'payee_name', orderable: false, className: 'min-desktop hide-panel' }, - dateColumn({data: 'expenditure_date', className: 'min-tablet hide-panel column--small'}), - currencyColumn({data: 'expenditure_amount', className: 'min-tablet hide-panel column--number'}), + dateColumn({ + data: 'expenditure_date', + className: 'min-tablet hide-panel column--small' + }), + currencyColumn({ + data: 'expenditure_amount', + className: 'min-tablet hide-panel column--number' + }), modalTriggerColumn ]; @@ -403,7 +510,10 @@ var individualContributions = [ orderable: false, className: 'all hide-panel-tablet', render: function(data, type, row, meta) { - if(data && !(_.contains(helpers.globals.EARMARKED_CODES, row.receipt_type))){ + if ( + data && + !_.contains(helpers.globals.EARMARKED_CODES, row.receipt_type) + ) { return columnHelpers.buildEntityLink( data.name, helpers.buildAppUrl(['committee', data.committee_id]), @@ -430,10 +540,24 @@ var individualContributions = [ } } }, - {data: 'contributor_state', orderable: false, className: 'min-desktop hide-panel column--state '}, - {data: 'contributor_employer', orderable: false, className: 'min-desktop hide-panel'}, - dateColumn({data: 'contribution_receipt_date', className: 'min-tablet hide-panel column--small'}), - currencyColumn({data: 'contribution_receipt_amount', className: 'min-tablet hide-panel column--number'}), + { + data: 'contributor_state', + orderable: false, + className: 'min-desktop hide-panel column--state ' + }, + { + data: 'contributor_employer', + orderable: false, + className: 'min-desktop hide-panel' + }, + dateColumn({ + data: 'contribution_receipt_date', + className: 'min-tablet hide-panel column--small' + }), + currencyColumn({ + data: 'contribution_receipt_amount', + className: 'min-tablet hide-panel column--number' + }), modalTriggerColumn ]; @@ -475,8 +599,14 @@ var partyCoordinatedExpenditures = [ orderable: false, className: 'min-desktop hide-panel' }, - dateColumn({data: 'expenditure_date', className: 'min-tablet hide-panel column--small'}), - currencyColumn({data: 'expenditure_amount', className: 'min-tablet hide-panel column--number'}), + dateColumn({ + data: 'expenditure_date', + className: 'min-tablet hide-panel column--small' + }), + currencyColumn({ + data: 'expenditure_amount', + className: 'min-tablet hide-panel column--number' + }), modalTriggerColumn ]; @@ -486,14 +616,16 @@ var receipts = [ orderable: false, className: 'all', render: function(data, type, row, meta) { - if(data && !(_.contains(helpers.globals.EARMARKED_CODES, row.receipt_type))){ + if ( + data && + !_.contains(helpers.globals.EARMARKED_CODES, row.receipt_type) + ) { return columnHelpers.buildEntityLink( data.name, helpers.buildAppUrl(['committee', data.committee_id]), 'committee' ); - } - else { + } else { return row.contributor_name; } } @@ -517,16 +649,26 @@ var receipts = [ { data: 'fec_election_type_desc', orderable: false, - className: 'min-desktop', + className: 'min-desktop' + }, + { + data: 'contributor_state', + orderable: false, + className: 'min-desktop hide-panel column--state' }, - {data: 'contributor_state', orderable: false, className: 'min-desktop hide-panel column--state'}, - dateColumn({data: 'contribution_receipt_date', className: 'min-tablet hide-panel column--small'}), - currencyColumn({data: 'contribution_receipt_amount', className: 'min-tablet hide-panel column--number'}), + dateColumn({ + data: 'contribution_receipt_date', + className: 'min-tablet hide-panel column--small' + }), + currencyColumn({ + data: 'contribution_receipt_amount', + className: 'min-tablet hide-panel column--number' + }), modalTriggerColumn ]; var reports = { - committee: { + committee: { data: 'committee_name', orderable: false, className: 'all', @@ -537,7 +679,9 @@ var reports = { className: 'all column--doc-download', orderable: false, render: function(data, type, row) { - var doc_description = row.document_description ? row.document_description : row.form_type; + var doc_description = row.document_description + ? row.document_description + : row.form_type; var amendment_version = helpers.amendmentVersionDescription(row); var pdf_url = row.pdf_url ? row.pdf_url : null; var csv_url = row.csv_url ? row.csv_url : null; @@ -596,7 +740,7 @@ var loans = [ data: 'committee', orderable: false, className: 'all', - render: function (data) { + render: function(data) { if (data) { return columnHelpers.buildEntityLink( data.name, @@ -611,23 +755,30 @@ var loans = [ { data: 'loan_source_name', orderable: false, - className: 'all', + className: 'all' }, - dateColumn({data: 'incurred_date', orderable: true, className: 'min-tablet hide-panel column--small'}), - currencyColumn({data: 'payment_to_date', className: 'min-desktop hide-panel column--number'}), - currencyColumn({data: 'original_loan_amount', className: 'min-desktop hide-panel column--number'}), + dateColumn({ + data: 'incurred_date', + orderable: true, + className: 'min-tablet hide-panel column--small' + }), + currencyColumn({ + data: 'payment_to_date', + className: 'min-desktop hide-panel column--number' + }), + currencyColumn({ + data: 'original_loan_amount', + className: 'min-desktop hide-panel column--number' + }), modalTriggerColumn ]; var audit = [ - columnHelpers.urlColumn - ('link_to_report', - { - data: 'committee_name', - className: 'all align-top', - orderable: true - } - ), + columnHelpers.urlColumn('link_to_report', { + data: 'committee_name', + className: 'all align-top', + orderable: true + }), { data: 'cycle', @@ -650,19 +801,21 @@ var audit = [ data: 'primary_category_list', className: 'all align-top', orderable: false, - render: function (data){ + render: function(data) { if (data) { - var html = '<ol class="list--numbered">' - for(var i in data){ - html += '<li>' + data[i]['primary_category_name'] + '<ol>' - for(var j in data[i]['sub_category_list']){ - html += '<li>'+ data[i]['sub_category_list'][j]['sub_category_name'] + '</li>' + var html = '<ol class="list--numbered">'; + for (var i in data) { + html += '<li>' + data[i]['primary_category_name'] + '<ol>'; + for (var j in data[i]['sub_category_list']) { + html += + '<li>' + + data[i]['sub_category_list'][j]['sub_category_name'] + + '</li>'; } - html += '</ol></li>' + html += '</ol></li>'; } - return html + '</ol>' - } - else { + return html + '</ol>'; + } else { return ''; } } @@ -672,7 +825,7 @@ var audit = [ data: 'candidate_name', className: 'min-tablet hide-panel column--small align-top', orderable: true - }, + } ]; module.exports = { diff --git a/fec/fec/static/js/modules/cycle-select.js b/fec/fec/static/js/modules/cycle-select.js index 65396ebe86..4282642fc1 100644 --- a/fec/fec/static/js/modules/cycle-select.js +++ b/fec/fec/static/js/modules/cycle-select.js @@ -43,11 +43,12 @@ CycleSelect.prototype.initCyclesMulti = function(selected) { max: cycle, electionFull: false, cycleId: cycleId, - active: typeof context !== 'undefined' && context.cycles ? - context.cycles.indexOf(cycle) !== -1 : - true, - checked: cycle.toString() === params.cycle && - params.electionFull === 'false' + active: + typeof context !== 'undefined' && context.cycles + ? context.cycles.indexOf(cycle) !== -1 + : true, + checked: + cycle.toString() === params.cycle && params.electionFull === 'false' }; }); bins.unshift({ @@ -56,7 +57,7 @@ CycleSelect.prototype.initCyclesMulti = function(selected) { electionFull: true, cycleId: cycleId, active: true, - checked: true, + checked: true }); this.$cycles.html(cyclesTemplate(bins)); this.$cycles.on('change', this.handleChange.bind(this)); @@ -72,7 +73,7 @@ CycleSelect.prototype.initCyclesSingle = function(selected) { var min = selected - 1; var max = selected; this.setTimePeriod(min, max); - this.$cycles.html(cycleTemplate({min: min, max: max})); + this.$cycles.html(cycleTemplate({ min: min, max: max })); }; CycleSelect.prototype.setTimePeriod = function(min, max) { @@ -149,16 +150,18 @@ PathCycleSelect.prototype.getParams = function() { PathCycleSelect.prototype.nextUrl = function(cycle, electionFull) { var uri = URI(window.location.href); - var path = uri.path() + var path = uri + .path() .replace(/^\/|\/$/g, '') .split('/') .slice(0, -1) .concat([cycle]) .join('/') .concat('/'); - return uri.path(path) - .search({election_full: electionFull}) + return uri + .path(path) + .search({ election_full: electionFull }) .toString(); }; -module.exports = {CycleSelect: CycleSelect}; +module.exports = { CycleSelect: CycleSelect }; diff --git a/fec/fec/static/js/modules/decoders.js b/fec/fec/static/js/modules/decoders.js index dc36d47c4b..a93810ccfd 100644 --- a/fec/fec/static/js/modules/decoders.js +++ b/fec/fec/static/js/modules/decoders.js @@ -10,7 +10,8 @@ var forms = { F3L: 'Bundled contributions reports', F4: 'Convention financial reports', F5: 'Independent expenditure reports and notices (by a person or group)', - F24: 'Independent expenditure reports and notices (by a registered committee)', + F24: + 'Independent expenditure reports and notices (by a registered committee)', F6: 'Contributions and loans notices', F7: 'Communication cost reports', F8: 'Debt settlement plans', @@ -75,62 +76,62 @@ var reports = { }; var states = { - AK: 'Alaska', - AL: 'Alabama', - AR: 'Arkansas', - AS: 'American Samoa', - AZ: 'Arizona', - CA: 'California', - CO: 'Colorado', - CT: 'Connecticut', - DC: 'District of Columbia', - DE: 'Delaware', - FL: 'Florida', - GA: 'Georgia', - GU: 'Guam', - HI: 'Hawaii', - IA: 'Iowa', - ID: 'Idaho', - IL: 'Illinois', - IN: 'Indiana', - KS: 'Kansas', - KY: 'Kentucky', - LA: 'Louisiana', - MA: 'Massachusetts', - MD: 'Maryland', - ME: 'Maine', - MI: 'Michigan', - MN: 'Minnesota', - MO: 'Missouri', - MP: 'Northern Mariana Islands', - MS: 'Mississippi', - MT: 'Montana', - NC: 'North Carolina', - ND: 'North Dakota', - NE: 'Nebraska', - NH: 'New Hampshire', - NJ: 'New Jersey', - NM: 'New Mexico', - NV: 'Nevada', - NY: 'New York', - OH: 'Ohio', - OK: 'Oklahoma', - OR: 'Oregon', - PA: 'Pennsylvania', - PR: 'Puerto Rico', - RI: 'Rhode Island', - SC: 'South Carolina', - SD: 'South Dakota', - TN: 'Tennessee', - TX: 'Texas', - UT: 'Utah', - VA: 'Virginia', - VI: 'Virgin Islands', - VT: 'Vermont', - WA: 'Washington', - WI: 'Wisconsin', - WV: 'West Virginia', - WY: 'Wyoming', + AK: 'Alaska', + AL: 'Alabama', + AR: 'Arkansas', + AS: 'American Samoa', + AZ: 'Arizona', + CA: 'California', + CO: 'Colorado', + CT: 'Connecticut', + DC: 'District of Columbia', + DE: 'Delaware', + FL: 'Florida', + GA: 'Georgia', + GU: 'Guam', + HI: 'Hawaii', + IA: 'Iowa', + ID: 'Idaho', + IL: 'Illinois', + IN: 'Indiana', + KS: 'Kansas', + KY: 'Kentucky', + LA: 'Louisiana', + MA: 'Massachusetts', + MD: 'Maryland', + ME: 'Maine', + MI: 'Michigan', + MN: 'Minnesota', + MO: 'Missouri', + MP: 'Northern Mariana Islands', + MS: 'Mississippi', + MT: 'Montana', + NC: 'North Carolina', + ND: 'North Dakota', + NE: 'Nebraska', + NH: 'New Hampshire', + NJ: 'New Jersey', + NM: 'New Mexico', + NV: 'Nevada', + NY: 'New York', + OH: 'Ohio', + OK: 'Oklahoma', + OR: 'Oregon', + PA: 'Pennsylvania', + PR: 'Puerto Rico', + RI: 'Rhode Island', + SC: 'South Carolina', + SD: 'South Dakota', + TN: 'Tennessee', + TX: 'Texas', + UT: 'Utah', + VA: 'Virginia', + VI: 'Virgin Islands', + VT: 'Vermont', + WA: 'Washington', + WI: 'Wisconsin', + WV: 'West Virginia', + WY: 'Wyoming' }; var parties = { @@ -177,15 +178,15 @@ var parties = { SWP: 'Socialist Workers Party', THD: 'Theo-Dem', TWR: 'Taxpayers Without Representation', - TX: 'Taxpayers', + TX: 'Taxpayers', USP: "US People's Party" }; module.exports = { - amendments: {A: 'Amendment', N: 'New'}, - office: {P: 'President', S: 'Senate', H: 'House of Representatives'}, - supportOppose: {S: 'Support', O: 'Oppose'}, - means: {'e-file': 'Electronic filing', 'paper': 'Paper filing'}, + amendments: { A: 'Amendment', N: 'New' }, + office: { P: 'President', S: 'Senate', H: 'House of Representatives' }, + supportOppose: { S: 'Support', O: 'Oppose' }, + means: { 'e-file': 'Electronic filing', paper: 'Paper filing' }, forms: forms, parties: parties, states: states, diff --git a/fec/fec/static/js/modules/download.js b/fec/fec/static/js/modules/download.js index c730612ad1..6fbd14fbe1 100644 --- a/fec/fec/static/js/modules/download.js +++ b/fec/fec/static/js/modules/download.js @@ -133,7 +133,7 @@ DownloadItem.prototype.refresh = function() { this.promise = $.ajax({ method: 'POST', url: this.apiUrl, - data: JSON.stringify({filename: this.filename}), + data: JSON.stringify({ filename: this.filename }), contentType: 'application/json', dataType: 'json' }); @@ -183,22 +183,24 @@ DownloadItem.prototype.close = function() { }; DownloadItem.prototype.handleServerError = function() { - // This is how we handle a 500 server error - // First, display a message - this.$body.html('<div class="message message--alert message--mini">' + - 'Sorry, there was a server error. Please try again later.' + - '</div>'); + // This is how we handle a 500 server error + // First, display a message + this.$body.html( + '<div class="message message--alert message--mini">' + + 'Sorry, there was a server error. Please try again later.' + + '</div>' + ); - // Clear all traces of the downloadUrl - window.clearTimeout(this.timeout); - this.promise && this.promise.abort(); - this.$body.removeClass('is-pending'); - this.$body.removeClass('download'); - window.localStorage.removeItem(this.key); + // Clear all traces of the downloadUrl + window.clearTimeout(this.timeout); + this.promise && this.promise.abort(); + this.$body.removeClass('is-pending'); + this.$body.removeClass('download'); + window.localStorage.removeItem(this.key); - // Tell the container to subtract an item, but preserve the DOM itself - // so that the message stays visible - this.container.subtract(true); + // Tell the container to subtract an item, but preserve the DOM itself + // so that the message stays visible + this.container.subtract(true); }; function DownloadContainer(parent) { @@ -212,14 +214,14 @@ function DownloadContainer(parent) { DownloadContainer.prototype.add = function() { this.items++; if (this.$body) { - this.$body.trigger({type: 'download:countChanged', count: this.items}); + this.$body.trigger({ type: 'download:countChanged', count: this.items }); } }; DownloadContainer.prototype.subtract = function(preserve) { this.items = this.items - 1; if (this.$body) { - this.$body.trigger({type: 'download:countChanged', count: this.items}); + this.$body.trigger({ type: 'download:countChanged', count: this.items }); } if (this.items === 0 && !preserve) { this.destroy(); @@ -233,7 +235,8 @@ DownloadContainer.prototype.destroy = function() { DownloadContainer.instance = null; DownloadContainer.getInstance = function(parent) { - DownloadContainer.instance = DownloadContainer.instance || new DownloadContainer(parent); + DownloadContainer.instance = + DownloadContainer.instance || new DownloadContainer(parent); return DownloadContainer.instance; }; diff --git a/fec/fec/static/js/modules/dropdowns.js b/fec/fec/static/js/modules/dropdowns.js index 5d6f86f500..71856c4d14 100644 --- a/fec/fec/static/js/modules/dropdowns.js +++ b/fec/fec/static/js/modules/dropdowns.js @@ -9,7 +9,7 @@ var KEYCODE_ESC = 27; var KEYCODE_ENTER = 13; var defaultOpts = { - checkboxes: true, + checkboxes: true }; /** @@ -29,12 +29,32 @@ function Dropdown(selector, opts) { if (this.opts.checkboxes) { this.$selected = this.$body.find('.dropdown__selected'); - this.$panel.on('keyup', 'input[type="checkbox"]', this.handleCheckKeyup.bind(this)); - this.$panel.on('change', 'input[type="checkbox"]', this.handleCheck.bind(this)); - this.$panel.on('click', '.dropdown__item--selected', this.handleDropdownItemClick.bind(this)); - - this.$selected.on('click', 'input[type="checkbox"]', this.handleSelectedInputClick.bind(this)); - this.$selected.on('click', '.dropdown__remove', this.handleRemoveClick.bind(this)); + this.$panel.on( + 'keyup', + 'input[type="checkbox"]', + this.handleCheckKeyup.bind(this) + ); + this.$panel.on( + 'change', + 'input[type="checkbox"]', + this.handleCheck.bind(this) + ); + this.$panel.on( + 'click', + '.dropdown__item--selected', + this.handleDropdownItemClick.bind(this) + ); + + this.$selected.on( + 'click', + 'input[type="checkbox"]', + this.handleSelectedInputClick.bind(this) + ); + this.$selected.on( + 'click', + '.dropdown__remove', + this.handleRemoveClick.bind(this) + ); if (this.isEmpty()) { this.removePanel(); @@ -52,7 +72,7 @@ function Dropdown(selector, opts) { // Set ARIA attributes this.$button.attr('aria-haspopup', 'true'); - this.$panel.attr('aria-label','More options'); + this.$panel.attr('aria-label', 'More options'); } Dropdown.prototype.toggle = function(e) { @@ -65,7 +85,7 @@ Dropdown.prototype.toggle = function(e) { Dropdown.prototype.show = function() { this.$panel.attr('aria-hidden', 'false'); - this.$panel.perfectScrollbar({suppressScrollX: true}); + this.$panel.perfectScrollbar({ suppressScrollX: true }); this.$panel.find('input[type="checkbox"]:first').focus(); this.$button.addClass('is-active'); this.isOpen = true; @@ -86,8 +106,12 @@ Dropdown.prototype.handleClickAway = function(e) { Dropdown.prototype.handleFocusAway = function(e) { var $target = $(e.target); - if (this.isOpen && !this.$panel.has($target).length && - !this.$panel.is($target) && !$target.is(this.$button)) { + if ( + this.isOpen && + !this.$panel.has($target).length && + !this.$panel.is($target) && + !$target.is(this.$button) + ) { this.hide(); } }; @@ -103,7 +127,9 @@ Dropdown.prototype.handleKeyup = function(e) { Dropdown.prototype.handleCheckKeyup = function(e) { if (e.keyCode === KEYCODE_ENTER) { - $(e.target).prop('checked', true).change(); + $(e.target) + .prop('checked', true) + .change(); } }; @@ -132,7 +158,9 @@ Dropdown.prototype.handleSelectedInputClick = function(e) { Dropdown.prototype.handleCheckboxRemoval = function($input) { var $item = $input.parent(); var $label = $input.parent().find('label'); - var $button = this.$panel.find('button[data-label="' + $input.attr('id') +'"]'); + var $button = this.$panel.find( + 'button[data-label="' + $input.attr('id') + '"]' + ); if ($button.length > 0) { $button.parent().append($input); @@ -144,7 +172,9 @@ Dropdown.prototype.handleCheckboxRemoval = function($input) { }; Dropdown.prototype.handleRemoveClick = function(e, opts) { - var $input = $(e.target).parent().find('input'); + var $input = $(e.target) + .parent() + .find('input'); // tag removal if (opts) { @@ -158,7 +188,7 @@ Dropdown.prototype.handleRemoveClick = function(e, opts) { Dropdown.prototype.handleClearFilters = function() { var self = this; if (this.$selected) { - this.$selected.find('input:checkbox:not(:checked)').each(function () { + this.$selected.find('input:checkbox:not(:checked)').each(function() { self.handleCheckboxRemoval($(this)); }); } @@ -170,21 +200,32 @@ Dropdown.prototype.selectItem = function($input) { var prev = $item.prevAll('.dropdown__item'); var next = $item.nextAll('.dropdown__item'); - $item.after('<li class="dropdown__item">' + - '<button class="dropdown__item--selected is-checked"' + - ' data-label="' + $label.attr('for') + '" >' + - $label.text() + '</button></li>'); + $item.after( + '<li class="dropdown__item">' + + '<button class="dropdown__item--selected is-checked"' + + ' data-label="' + + $label.attr('for') + + '" >' + + $label.text() + + '</button></li>' + ); this.$selected.append($item); - $item.append('<button class="dropdown__remove">' + - '<span class="u-visually-hidden">Remove</span></button>'); + $item.append( + '<button class="dropdown__remove">' + + '<span class="u-visually-hidden">Remove</span></button>' + ); if (!this.isEmpty()) { if (next.length) { - $(next[0]).find('input[type="checkbox"]').focus(); + $(next[0]) + .find('input[type="checkbox"]') + .focus(); } else if (prev.length) { - $(prev[0]).find('input[type="checkbox"]').focus(); + $(prev[0]) + .find('input[type="checkbox"]') + .focus(); } } else { this.removePanel(); @@ -205,4 +246,4 @@ Dropdown.prototype.destroy = function() { this.events.clear(); }; -module.exports = {Dropdown: Dropdown}; +module.exports = { Dropdown: Dropdown }; diff --git a/fec/fec/static/js/modules/election-form.js b/fec/fec/static/js/modules/election-form.js index 0149a362e8..5e82dfa1af 100644 --- a/fec/fec/static/js/modules/election-form.js +++ b/fec/fec/static/js/modules/election-form.js @@ -39,7 +39,6 @@ ElectionForm.prototype.hasOption = function($select, value) { * If there's a zip field, it clears that. */ ElectionForm.prototype.handleStateChange = function() { - var state = this.$state.val(); this.updateDistricts(state); if (state && this.$zip) { @@ -58,10 +57,12 @@ ElectionForm.prototype.updateDistricts = function(state) { this.districts = districts[state] ? districts[state].districts : 0; if (this.districts) { this.$district - .html(districtTemplate({ - districts: _.range(1, this.districts + 1), - senate: this.showSenateOption - })) + .html( + districtTemplate({ + districts: _.range(1, this.districts + 1), + senate: this.showSenateOption + }) + ) .val('') .prop('disabled', false); } else if (this.showSenateOption) { @@ -70,17 +71,18 @@ ElectionForm.prototype.updateDistricts = function(state) { // navigate to the house page. // If showSenateOption is true, we also want to show an at-large house district this.$district - .html(districtTemplate({ - districts: null, - atLargeHouse: true, - senate: this.showSenateOption - })) + .html( + districtTemplate({ + districts: null, + atLargeHouse: true, + senate: this.showSenateOption + }) + ) .val('') .prop('disabled', false); } else { this.$district.prop('disabled', true); } - }; /** @@ -88,7 +90,7 @@ ElectionForm.prototype.updateDistricts = function(state) { * @param {object} query - the query to pass to the URL */ ElectionForm.prototype.getUrl = function(query) { - var params = _.extend({}, {per_page: 100}, query); + var params = _.extend({}, { per_page: 100 }, query); return helpers.buildUrl(['elections', 'search'], params); }; diff --git a/fec/fec/static/js/modules/election-lookup.js b/fec/fec/static/js/modules/election-lookup.js index 5d0d7bfe8b..4f6bfc37ab 100644 --- a/fec/fec/static/js/modules/election-lookup.js +++ b/fec/fec/static/js/modules/election-lookup.js @@ -11,7 +11,7 @@ var ElectionMap = require('./election-map').ElectionMap; * The simpler form of the full ElectionSearch tool, used on the data landing page * This component has a map and the state and district selects * Inherits from the ElectionForm class -*/ + */ function ElectionLookup(selector) { this.$elm = $(selector); @@ -32,7 +32,9 @@ ElectionLookup.prototype = Object.create(ElectionForm.prototype); ElectionLookup.constructor = ElectionLookup; ElectionLookup.prototype.init = function() { - if (this.initialized) { return; } + if (this.initialized) { + return; + } this.$state.on('change', this.handleStateChange.bind(this)); this.$district.on('change', this.handleDistrictChange.bind(this)); @@ -96,5 +98,5 @@ ElectionLookup.prototype.search = function(e) { }; module.exports = { - ElectionLookup: ElectionLookup, + ElectionLookup: ElectionLookup }; diff --git a/fec/fec/static/js/modules/election-map.js b/fec/fec/static/js/modules/election-map.js index 1947edaeaa..6f87859ba9 100644 --- a/fec/fec/static/js/modules/election-map.js +++ b/fec/fec/static/js/modules/election-map.js @@ -20,7 +20,7 @@ var defaultOpts = { }; var boundsOverrides = { - 200: {coords: [64.06, -152.23], zoom: 3} + 200: { coords: [64.06, -152.23], zoom: 3 } }; function getStatePalette(scale) { @@ -91,7 +91,9 @@ ElectionMap.prototype.init = function() { * Draw state overlays */ ElectionMap.prototype.drawStates = function() { - if (this.featureType === FEATURE_TYPES.STATES) { return; } + if (this.featureType === FEATURE_TYPES.STATES) { + return; + } this.featureType = FEATURE_TYPES.STATES; if (this.overlay) { this.map.removeLayer(this.overlay); @@ -107,11 +109,13 @@ ElectionMap.prototype.drawStates = function() { * @param {array} districts - array of unique district identifiers */ ElectionMap.prototype.drawDistricts = function(districts) { - if (this.featureType === FEATURE_TYPES.DISTRICTS && !districts) { return; } + if (this.featureType === FEATURE_TYPES.DISTRICTS && !districts) { + return; + } this.featureType = FEATURE_TYPES.DISTRICTS; - var features = districts ? - this.filterDistricts(districts) : - utils.districtFeatures; + var features = districts + ? this.filterDistricts(districts) + : utils.districtFeatures; if (this.overlay) { this.map.removeLayer(this.overlay); } @@ -128,32 +132,41 @@ ElectionMap.prototype.drawDistricts = function(districts) { * @param {array} districts - array of unique district identifiers */ ElectionMap.prototype.updateBounds = function(districts) { - var rule = districts && _.find(boundsOverrides, function(rule, district) { - return districts.indexOf(parseInt(district)) !== -1; - }); + var rule = + districts && + _.find(boundsOverrides, function(rule, district) { + return districts.indexOf(parseInt(district)) !== -1; + }); this._viewReset = !!(rule || districts); if (rule) { this.map.setView(rule.coords, rule.zoom); - } - else if (districts) { + } else if (districts) { this.map.fitBounds(this.overlay.getBounds()); } }; ElectionMap.prototype.drawBackgroundDistricts = function(districts) { - if (!districts) { return; } + if (!districts) { + return; + } var states = _.chain(districts) .map(function(district) { return Math.floor(district / 100); }) .unique() .value(); - var stateDistricts = _.filter(utils.districtFeatures.features, function(feature) { - return states.indexOf(Math.floor(feature.id / 100)) !== -1 && - districts.indexOf(feature.id) === -1; + var stateDistricts = _.filter(utils.districtFeatures.features, function( + feature + ) { + return ( + states.indexOf(Math.floor(feature.id / 100)) !== -1 && + districts.indexOf(feature.id) === -1 + ); }); L.geoJson(stateDistricts, { - onEachFeature: _.partial(this.onEachDistrict.bind(this), _, _, {color: '#bbbbbb'}) + onEachFeature: _.partial(this.onEachDistrict.bind(this), _, _, { + color: '#bbbbbb' + }) }).addTo(this.overlay); }; @@ -177,7 +190,7 @@ ElectionMap.prototype.handleTileLoad = function(e) { ElectionMap.prototype.onEachState = function(feature, layer) { var color = this.statePalette[feature.id % this.statePalette.length]; - layer.setStyle({color: color}); + layer.setStyle({ color: color }); layer.on('click', this.handleStateClick.bind(this)); }; @@ -186,7 +199,7 @@ ElectionMap.prototype.onEachDistrict = function(feature, layer, opts) { var decoded = utils.decodeDistrict(feature.id); var palette = this.districtPalette[decoded.state]; var color = palette[decoded.district % palette.length]; - layer.setStyle({color: opts.color || color}); + layer.setStyle({ color: opts.color || color }); layer.on('click', this.handleDistrictClick.bind(this)); }; @@ -231,5 +244,5 @@ ElectionMap.prototype.show = function() { }; module.exports = { - ElectionMap: ElectionMap, + ElectionMap: ElectionMap }; diff --git a/fec/fec/static/js/modules/election-search.js b/fec/fec/static/js/modules/election-search.js index a0d13c5e72..79f3c4219a 100644 --- a/fec/fec/static/js/modules/election-search.js +++ b/fec/fec/static/js/modules/election-search.js @@ -69,20 +69,20 @@ ElectionSearch.prototype = Object.create(ElectionForm.prototype); ElectionSearch.constructor = ElectionSearch; ElectionSearch.prototype.updateRedistrictingMessage = function() { - if(this.$cycle.val() == 2018 && this.$state.val() == 'PA') { + if (this.$cycle.val() == 2018 && this.$state.val() == 'PA') { $('.pa-message').show(); } else { $('.pa-message').hide(); } -} +}; ElectionSearch.prototype.performSearch = function() { this.search(); this.updateRedistrictingMessage(); -} +}; ElectionSearch.prototype.performStateChange = function() { this.handleStateChange(); this.updateRedistrictingMessage(); -} +}; /** * Call the API to get a list of upcoming election dates */ @@ -91,14 +91,14 @@ ElectionSearch.prototype.getUpcomingElections = function() { var month = now.getMonth() + 1; var today = now.getFullYear() + '-' + month + '-' + now.getDate(); var query = { - 'sort': 'election_date', - 'min_election_date': today + sort: 'election_date', + min_election_date: today }; var url = helpers.buildUrl(['election-dates'], query); var self = this; if (Number(this.$cycle.val()) >= now.getFullYear()) { $.getJSON(url).done(function(response) { - self.upcomingElections = response.results; + self.upcomingElections = response.results; }); } }; @@ -128,12 +128,15 @@ ElectionSearch.prototype.handleSelectMap = function(state, district) { }; /** - * Hack to remove the presidential result in non-presidential years - * Eventually this will be handled by the API - * @param {array} results - Array of API results - * @param {int} cycle - The even-year value of a cycle + * Hack to remove the presidential result in non-presidential years + * Eventually this will be handled by the API + * @param {array} results - Array of API results + * @param {int} cycle - The even-year value of a cycle */ -ElectionSearch.prototype.removeWrongPresidentialElections = function(results, cycle) { +ElectionSearch.prototype.removeWrongPresidentialElections = function( + results, + cycle +) { if (Number(cycle) % 4 > 0) { return _.filter(results, function(result) { return result.office !== 'P'; @@ -150,7 +153,7 @@ ElectionSearch.prototype.removeWrongPresidentialElections = function(results, cy */ ElectionSearch.prototype.search = function(e, opts) { e && e.preventDefault(); - opts = _.extend({pushState: true}, opts || {}); + opts = _.extend({ pushState: true }, opts || {}); var self = this; var serialized = self.serialize(); if (self.shouldSearch(serialized)) { @@ -158,7 +161,10 @@ ElectionSearch.prototype.search = function(e, opts) { // Requested search options differ from saved options; request new data. self.xhr && self.xhr.abort(); self.xhr = $.getJSON(self.getUrl(serialized)).done(function(response) { - self.results = self.removeWrongPresidentialElections(response.results, serialized.cycle); + self.results = self.removeWrongPresidentialElections( + response.results, + serialized.cycle + ); // Note: Update district color map before rendering results var encodedDistricts = self.encodeDistricts(self.results); self.map.drawDistricts(encodedDistricts); @@ -166,7 +172,13 @@ ElectionSearch.prototype.search = function(e, opts) { }); self.serialized = serialized; if (opts.pushState) { - window.history.pushState(serialized, null, URI('').query(serialized).toString()); + window.history.pushState( + serialized, + null, + URI('') + .query(serialized) + .toString() + ); analytics.pageView(); } } else if (self.results) { @@ -190,7 +202,7 @@ ElectionSearch.prototype.handlePopState = function() { this.handleStateChange(); this.$district.val(params.district); this.$cycle.val(params.cycle || this.$cycle.val()); - this.performSearch(null, {pushState: false}); + this.performSearch(null, { pushState: false }); }; ElectionSearch.prototype.shouldSearch = function(serialized) { @@ -224,7 +236,7 @@ ElectionSearch.prototype.draw = function(results) { this.map.hide(); } else { this.map.show(); - } + } }; /** @@ -240,17 +252,23 @@ ElectionSearch.prototype.drawResult = function(result) { var election = this.formatResult(result, this); var upcomingElections = _.filter(this.upcomingElections, function(upcoming) { if (election.office === 'H') { - return upcoming.election_state === election.state && - upcoming.election_district === Number(election.district); + return ( + upcoming.election_state === election.state && + upcoming.election_district === Number(election.district) + ); } else if (election.office === 'S') { - return upcoming.election_state === election.state && - upcoming.office_sought === election.office; + return ( + upcoming.election_state === election.state && + upcoming.office_sought === election.office + ); } }); if (upcomingElections.length > 0) { var parsed = moment(upcomingElections[0].election_date, 'YYYY-MM-DD'); - election.electionDate = parsed.isValid() ? parsed.format('MMMM Do, YYYY') : ''; + election.electionDate = parsed.isValid() + ? parsed.format('MMMM Do, YYYY') + : ''; election.electionType = upcomingElections[0].election_type_full; this.$resultsItems.append(resultTemplate(election)); } else { @@ -272,8 +290,11 @@ ElectionSearch.prototype.drawZipWarning = function() { */ ElectionSearch.prototype.updateLocations = function() { var self = this; - var svg = self.$svg || $.get('/static/img/i-map--primary.svg', '', null, 'xml') - .then(function(document) { + var svg = + self.$svg || + $.get('/static/img/i-map--primary.svg', '', null, 'xml').then(function( + document + ) { self.$svg = $(document.querySelector('svg')); return self.$svg; }); @@ -306,7 +327,7 @@ ElectionSearch.prototype.getTitle = function() { } else { title += ' in ' + decoders.states[params.state]; if (params.district && params.district !== '00') { - title += ', district ' + params.district; + title += ', district ' + params.district; } } return title; @@ -322,7 +343,7 @@ ElectionSearch.prototype.formatResult = function(result) { electionName: this.formatName(result), incumbent: this.formatIncumbent(result), color: this.formatColor(result), - url: this.formatUrl(result), + url: this.formatUrl(result) }); }; @@ -359,12 +380,9 @@ ElectionSearch.prototype.formatGenericElectionDate = function(result) { while (date.format('E') !== '1') { date = date.add(1, 'day'); } - return date - .add(1, 'day') - .format('MMMM Do, YYYY'); + return date.add(1, 'day').format('MMMM Do, YYYY'); }; - /** * If the result has an incumber, this formats the name of the person * @param {object} result diff --git a/fec/fec/static/js/modules/election-summary.js b/fec/fec/static/js/modules/election-summary.js index 73a5e0927b..4e83cebfe7 100644 --- a/fec/fec/static/js/modules/election-summary.js +++ b/fec/fec/static/js/modules/election-summary.js @@ -40,4 +40,4 @@ ElectionSummary.prototype.draw = function(response) { this.$expenditures.text(helpers.currency(response.independent_expenditures)); }; -module.exports = {ElectionSummary: ElectionSummary}; +module.exports = { ElectionSummary: ElectionSummary }; diff --git a/fec/fec/static/js/modules/election-utils.js b/fec/fec/static/js/modules/election-utils.js index ebbe45cf04..c2ca2d3c53 100644 --- a/fec/fec/static/js/modules/election-utils.js +++ b/fec/fec/static/js/modules/election-utils.js @@ -8,7 +8,7 @@ var helpers = require('./helpers'); var moment = require('moment'); var topojson = require('topojson'); -var sprintf = require('sprintf-js').sprintf +var sprintf = require('sprintf-js').sprintf; var electionDatesTemplate = require('../templates/electionDates.hbs'); var electionOfficesTemplate = require('../templates/electionOffices.hbs'); @@ -17,7 +17,10 @@ var districts = require('../data/districts.json'); var districtFeatures = topojson.feature(districts, districts.objects.districts); function encodeDistrict(state, district) { - return fips.fipsByState[state.toUpperCase()].STATE * 100 + (parseInt(district) || 0); + return ( + fips.fipsByState[state.toUpperCase()].STATE * 100 + + (parseInt(district) || 0) + ); } function decodeDistrict(district) { @@ -45,8 +48,7 @@ function truncate(value, digits) { */ function findDistrict(district) { return _.find(districtFeatures.features, function(feature) { - return feature.id === district || - truncate(feature.id, 2) === district; + return feature.id === district || truncate(feature.id, 2) === district; }); } @@ -58,27 +60,32 @@ function findDistrict(district) { */ function findDistricts(districts) { return _.filter(districtFeatures.features, function(feature) { - return districts.indexOf(feature.id) !== -1 || - districts.indexOf(truncate(feature.id, 2)) !== -1; + return ( + districts.indexOf(feature.id) !== -1 || + districts.indexOf(truncate(feature.id, 2)) !== -1 + ); }); } function getElections(state, office, cycle) { var officeSymbol = { - 'house': 'H', - 'senate': 'S', - 'president': 'P' + house: 'H', + senate: 'S', + president: 'P' }; var defaultQuery = { - 'sort': '-election_date', - 'per_page': 2, - 'election_type_id': 'G', - 'election_state': state, - 'office_sought': officeSymbol[office] + sort: '-election_date', + per_page: 2, + election_type_id: 'G', + election_state: state, + office_sought: officeSymbol[office] }; - var query = office !== 'president' ? defaultQuery : _.omit(defaultQuery, 'election_state'); + var query = + office !== 'president' + ? defaultQuery + : _.omit(defaultQuery, 'election_state'); var url = helpers.buildUrl(['election-dates'], query); var $election_dates_results = $('.election-dates'); diff --git a/fec/fec/static/js/modules/feedback.js b/fec/fec/static/js/modules/feedback.js index 0e6bce0671..094628c0a9 100644 --- a/fec/fec/static/js/modules/feedback.js +++ b/fec/fec/static/js/modules/feedback.js @@ -68,12 +68,15 @@ Feedback.prototype.submit = function(e) { * http://stackoverflow.com/questions/5100539/django-csrf-check-failing-with-an-ajax-post-request */ $.ajaxSetup({ - beforeSend: function(xhr, settings) { - if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) { - // Only send the token to relative URLs i.e. locally. - xhr.setRequestHeader('X-CSRFToken', $('input[name="csrfmiddlewaretoken"]').val()); - } - } + beforeSend: function(xhr, settings) { + if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) { + // Only send the token to relative URLs i.e. locally. + xhr.setRequestHeader( + 'X-CSRFToken', + $('input[name="csrfmiddlewaretoken"]').val() + ); + } + } }); e.preventDefault(); @@ -110,8 +113,10 @@ Feedback.prototype.submit = function(e) { Feedback.prototype.handleSuccess = function(response) { var message = '<h2 class="feedback__title">Thanks for helping us improve</h2>' + - '<p>This information has been reported on GitHub, where it\'s publicly visible. ' + - '<a href="' + response.html_url + '">Track the status of your feedback</a>.</p>'; + "<p>This information has been reported on GitHub, where it's publicly visible. " + + '<a href="' + + response.html_url + + '">Track the status of your feedback</a>.</p>'; var buttonText = 'Submit another issue'; this.$box.find('textarea').val(''); this.message(message, buttonText, 'success'); @@ -141,4 +146,4 @@ Feedback.prototype.reset = function() { this.$status.attr('aria-hidden', true); }; -module.exports = {Feedback: Feedback}; +module.exports = { Feedback: Feedback }; diff --git a/fec/fec/static/js/modules/filings.js b/fec/fec/static/js/modules/filings.js index 809646e8c0..4def6113c4 100644 --- a/fec/fec/static/js/modules/filings.js +++ b/fec/fec/static/js/modules/filings.js @@ -24,17 +24,16 @@ function resolveTemplate(row) { } function fetchReportDetails(row) { - var url = helpers.buildUrl( - ['committee', row.committee_id, 'reports'], - {beginning_image_number: row.beginning_image_number} - ); + var url = helpers.buildUrl(['committee', row.committee_id, 'reports'], { + beginning_image_number: row.beginning_image_number + }); return $.getJSON(url).then(function(response) { - var result = response.results.length ? - response.results[0] : - {}; + var result = response.results.length ? response.results[0] : {}; result.amendment_version = helpers.amendmentVersion(result.most_recent); - result.amendment_version_description = helpers.amendmentVersionDescription(row); + result.amendment_version_description = helpers.amendmentVersionDescription( + row + ); return _.extend({}, row, result); }); diff --git a/fec/fec/static/js/modules/filters-event.js b/fec/fec/static/js/modules/filters-event.js index 229469fec4..8614d932fd 100644 --- a/fec/fec/static/js/modules/filters-event.js +++ b/fec/fec/static/js/modules/filters-event.js @@ -8,7 +8,11 @@ var URI = require('urijs'); function lineNumberFilters() { lineNumberFiltersCheck(); - $('#filters').on('change', 'input,select', _.debounce(lineNumberFiltersCheck, 250)); + $('#filters').on( + 'change', + 'input,select', + _.debounce(lineNumberFiltersCheck, 250) + ); } function lineNumberFiltersCheck() { @@ -17,8 +21,7 @@ function lineNumberFiltersCheck() { if (Number(params.two_year_transaction_period) < 2007) { $('.js-line-number-filters').hide(); $('.js-line-number-message').show(); - } - else { + } else { $('.js-line-number-filters').show(); $('.js-line-number-message').hide(); } diff --git a/fec/fec/static/js/modules/filters/checkbox-filter.js b/fec/fec/static/js/modules/filters/checkbox-filter.js index 313eed3436..6713a2fe06 100644 --- a/fec/fec/static/js/modules/filters/checkbox-filter.js +++ b/fec/fec/static/js/modules/filters/checkbox-filter.js @@ -22,8 +22,7 @@ CheckboxFilter.constructor = CheckboxFilter; CheckboxFilter.prototype.handleChange = function(e) { var $input = $(e.target); var id = $input.attr('id'); - var loadedOnce, - eventName; + var loadedOnce, eventName; var $label = this.$elm.find('label[for="' + id + '"]'); loadedOnce = $input.data('loaded-once') || false; @@ -34,7 +33,9 @@ CheckboxFilter.prototype.handleChange = function(e) { // dropdown loading status if ($input.parent().hasClass('dropdown__item')) { - this.$elm.find('button[data-name="' + $input.attr('name') + '"]').addClass('is-loading'); + this.$elm + .find('button[data-name="' + $input.attr('name') + '"]') + .addClass('is-loading'); } } @@ -65,9 +66,9 @@ CheckboxFilter.prototype.removeCheckbox = function(e, opts) { // "Clear all filters" will remove unchecked checkboxes CheckboxFilter.prototype.handleClearFilters = function() { var self = this; - this.$elm.find('input:checkbox:not(:checked)').each(function () { - self.removeCheckbox({target: this}); + this.$elm.find('input:checkbox:not(:checked)').each(function() { + self.removeCheckbox({ target: this }); }); }; -module.exports = {CheckboxFilter: CheckboxFilter}; +module.exports = { CheckboxFilter: CheckboxFilter }; diff --git a/fec/fec/static/js/modules/filters/date-filter.js b/fec/fec/static/js/modules/filters/date-filter.js index fa80e3a786..6905664ab1 100644 --- a/fec/fec/static/js/modules/filters/date-filter.js +++ b/fec/fec/static/js/modules/filters/date-filter.js @@ -31,7 +31,11 @@ function DateFilter(elm) { this.$minDate.on('focus', this.handleMinDateSelect.bind(this)); this.$maxDate.on('focus', this.handleMaxDateSelect.bind(this)); - this.$elm.on('click', '.date-range__grid li', this.handleGridItemSelect.bind(this)); + this.$elm.on( + 'click', + '.date-range__grid li', + this.handleGridItemSelect.bind(this) + ); $(document.body).on('filter:modify', this.handleModifyEvent.bind(this)); $(document.body).on('tag:removeAll', this.handleRemoveAll.bind(this)); @@ -91,24 +95,28 @@ DateFilter.prototype.handleInputChange = function(e) { }; DateFilter.prototype.validate = function() { - if (!this.validateInput) { return; } + if (!this.validateInput) { + return; + } var years = [this.minYear, this.maxYear]; - var minDateYear = this.$minDate.val() ? - parseInt(this.$minDate.val().split('/')[2]) : this.minYear; - var maxDateYear = this.$maxDate.val() ? - parseInt(this.$maxDate.val().split('/')[2]) : this.maxYear; - if ( years.indexOf(minDateYear) > -1 && years.indexOf(maxDateYear) > -1 ) { + var minDateYear = this.$minDate.val() + ? parseInt(this.$minDate.val().split('/')[2]) + : this.minYear; + var maxDateYear = this.$maxDate.val() + ? parseInt(this.$maxDate.val().split('/')[2]) + : this.maxYear; + if (years.indexOf(minDateYear) > -1 && years.indexOf(maxDateYear) > -1) { this.hideWarning(); this.$elm.trigger('filters:validation', [ { - isValid: true, + isValid: true } ]); } else { this.showWarning(); this.$elm.trigger('filters:validation', [ { - isValid: false, + isValid: false } ]); } @@ -116,10 +124,7 @@ DateFilter.prototype.validate = function() { DateFilter.prototype.fromQuery = function(query) { if (query['min_' + this.name] || query['max_' + this.name]) { - this.setValue([ - query['min_' + this.name], - query['max_' + this.name] - ]); + this.setValue([query['min_' + this.name], query['max_' + this.name]]); } return this; }; @@ -155,12 +160,12 @@ DateFilter.prototype.handleRemoveAll = function(e, opts) { function remove($filter) { $filter.val(''); $filter.data('had-value', false); - $filter.trigger('filter:removed', {loadedOnce: true}); + $filter.trigger('filter:removed', { loadedOnce: true }); } if (forceRemove) { - remove(this.$minDate); - remove(this.$maxDate); + remove(this.$minDate); + remove(this.$maxDate); } }; @@ -184,10 +189,22 @@ DateFilter.prototype.setupDateGrid = function() { dateRangeSecond.find('ul').attr('data-year', this.maxYear); // get the elements of the beginning and ending dates - $dateBegin = this.$grid.find('ul[data-year="' + minDateYear + '"] ' + - 'li[data-month="' + minDateMonth + '"]'); - $dateEnd = this.$grid.find('ul[data-year="' + maxDateYear + '"] ' + - 'li[data-month="' + maxDateMonth + '"]'); + $dateBegin = this.$grid.find( + 'ul[data-year="' + + minDateYear + + '"] ' + + 'li[data-month="' + + minDateMonth + + '"]' + ); + $dateEnd = this.$grid.find( + 'ul[data-year="' + + maxDateYear + + '"] ' + + 'li[data-month="' + + maxDateMonth + + '"]' + ); // set the selected date range in the grid this.handleDateGridRange($dateBegin, $dateEnd); @@ -210,20 +227,28 @@ DateFilter.prototype.handleMinDateSelect = function() { var $dateBegin = this.$grid.find('.month--begin'); var $dateEnd = this.$grid.find('.month--end'); - this.$grid.show().removeClass('pick-max').addClass('pick-min'); + this.$grid + .show() + .removeClass('pick-max') + .addClass('pick-min'); this.$grid.find('.is-active').removeClass('is-active'); $dateBegin.addClass('is-active'); this.$grid.find('li').hover( function() { - var dateBeginNum = parseInt($(this).parent().attr('data-year') + $(this).attr('data-month')); - var dateEndNum = parseInt($dateEnd.parent().attr('data-year') + $dateEnd.attr('data-month')); + var dateBeginNum = parseInt( + $(this) + .parent() + .attr('data-year') + $(this).attr('data-month') + ); + var dateEndNum = parseInt( + $dateEnd.parent().attr('data-year') + $dateEnd.attr('data-month') + ); if (dateBeginNum <= dateEndNum) { self.$grid.removeClass('is-invalid'); self.handleDateGridRange($(this), $dateEnd); - } - else { + } else { self.$grid.addClass('is-invalid'); } }, @@ -239,7 +264,10 @@ DateFilter.prototype.handleMaxDateSelect = function() { var $dateBegin = this.$grid.find('.month--begin'); var $dateEnd = this.$grid.find('.month--end'); - this.$grid.show().removeClass('pick-min').addClass('pick-max'); + this.$grid + .show() + .removeClass('pick-min') + .addClass('pick-max'); this.$grid.find('.is-active').removeClass('is-active'); $dateEnd.addClass('is-active'); @@ -247,16 +275,19 @@ DateFilter.prototype.handleMaxDateSelect = function() { function() { // turn dates to numbers for comparsion // to make sure hover date range is valid - var dateBeginNum = - parseInt($dateBegin.parent().attr('data-year') + $dateBegin.attr('data-month')); - var dateEndNum = - parseInt($(this).parent().attr('data-year') + $(this).attr('data-month')); + var dateBeginNum = parseInt( + $dateBegin.parent().attr('data-year') + $dateBegin.attr('data-month') + ); + var dateEndNum = parseInt( + $(this) + .parent() + .attr('data-year') + $(this).attr('data-month') + ); if (dateBeginNum <= dateEndNum) { self.$grid.removeClass('is-invalid'); self.handleDateGridRange($dateBegin, $(this)); - } - else { + } else { self.$grid.addClass('is-invalid'); } }, @@ -281,18 +312,19 @@ DateFilter.prototype.handleGridItemSelect = function(e) { if (this.$grid.hasClass('pick-min')) { value[0] = selectDateMonth + '/01/' + selectDateYear; value[1] = this.$maxDate.val(); - } - else { + } else { // calculate last day of month for end date var lastDay = new Date(selectDateYear, selectDateMonth, 0); lastDay = lastDay.getDate(); value[0] = this.$minDate.val(); - value[1] = selectDateMonth + '/' + lastDay +'/' + selectDateYear; + value[1] = selectDateMonth + '/' + lastDay + '/' + selectDateYear; } if (!this.$grid.hasClass('is-invalid')) { - var $nextItem = this.$grid.hasClass('pick-min') ? this.$maxDate : this.$submit; + var $nextItem = this.$grid.hasClass('pick-min') + ? this.$maxDate + : this.$submit; this.$grid.removeClass('pick-min pick-max'); this.$grid.find('li').unbind('mouseenter mouseleave'); this.setValue(value); @@ -304,11 +336,15 @@ DateFilter.prototype.handleGridItemSelect = function(e) { DateFilter.prototype.showWarning = function() { if (!this.showingWarning) { var warning = - '<div class="message message--error message--small">' + - 'You entered a date that\'s outside the two-year time period. ' + + '<div class="message message--error message--small">' + + "You entered a date that's outside the two-year time period. " + 'Please enter a receipt date from ' + - '<strong>' + this.minYear + '-' + this.maxYear + '</strong>' + - '</div>'; + '<strong>' + + this.minYear + + '-' + + this.maxYear + + '</strong>' + + '</div>'; this.$range.after(warning); this.showingWarning = true; this.$grid.hide(); @@ -322,4 +358,4 @@ DateFilter.prototype.hideWarning = function() { } }; -module.exports = {DateFilter: DateFilter}; +module.exports = { DateFilter: DateFilter }; diff --git a/fec/fec/static/js/modules/filters/election-filter.js b/fec/fec/static/js/modules/filters/election-filter.js index c39ed08d32..20e319efd7 100644 --- a/fec/fec/static/js/modules/filters/election-filter.js +++ b/fec/fec/static/js/modules/filters/election-filter.js @@ -18,8 +18,12 @@ function ElectionFilter(elm) { this.$election = this.$elm.find('.js-election'); this.$cycles = this.$elm.find('.js-cycles'); - this.$cycle = this.$elm.find('input[type="hidden"][name="' + this.cycleName + '"]'); - this.$full = this.$elm.find('input[type="hidden"][name="' + this.fullName + '"]'); + this.$cycle = this.$elm.find( + 'input[type="hidden"][name="' + this.cycleName + '"]' + ); + this.$full = this.$elm.find( + 'input[type="hidden"][name="' + this.fullName + '"]' + ); this.loadedOnce = false; this.$election.on('change', this.handleElectionChange.bind(this)); @@ -37,8 +41,9 @@ ElectionFilter.prototype.fromQuery = function(query) { var full = query[this.fullName] !== null ? query[this.fullName] : true; if (election) { this.$election.val(election); - this.handleElectionChange({target: this.$election}); - this.$cycles.find('input[value="' + cycle + ':' + full + '"]') + this.handleElectionChange({ target: this.$election }); + this.$cycles + .find('input[value="' + cycle + ':' + full + '"]') .prop('checked', true) .change(); } @@ -53,15 +58,18 @@ ElectionFilter.prototype.handleElectionChange = function(e) { } var election = parseInt($(e.target).val()); var cycles = _.range(election - this.duration + 2, election + 2, 2); - var bins = _.map(cycles, function(cycle) { - return { - name: this.name, - cycle: cycle, - min: cycle - 1, - max: cycle, - full: false - }; - }.bind(this)); + var bins = _.map( + cycles, + function(cycle) { + return { + name: this.name, + cycle: cycle, + min: cycle - 1, + max: cycle, + full: false + }; + }.bind(this) + ); bins.unshift({ name: this.name, cycle: election, @@ -70,12 +78,21 @@ ElectionFilter.prototype.handleElectionChange = function(e) { full: true }); this.$cycles.html(cyclesTemplate(bins)); - this.$cycles.find('input').eq(0).prop('checked', true).change(); + this.$cycles + .find('input') + .eq(0) + .prop('checked', true) + .change(); }; ElectionFilter.prototype.handleCycleChange = function(e) { - var selected = $(e.target).val().split(':'); - this.$cycle.val(selected[0]).change().attr('checked', true); + var selected = $(e.target) + .val() + .split(':'); + this.$cycle + .val(selected[0]) + .change() + .attr('checked', true); this.$full.val(selected[1]).change(); this.setTag(); }; @@ -96,4 +113,4 @@ ElectionFilter.prototype.setTag = function() { this.loadedOnce = true; }; -module.exports = {ElectionFilter: ElectionFilter}; +module.exports = { ElectionFilter: ElectionFilter }; diff --git a/fec/fec/static/js/modules/filters/filter-base.js b/fec/fec/static/js/modules/filters/filter-base.js index cda5c11cee..39fa3adb42 100644 --- a/fec/fec/static/js/modules/filters/filter-base.js +++ b/fec/fec/static/js/modules/filters/filter-base.js @@ -10,9 +10,7 @@ function ensureArray(value) { } function prepareValue($elm, value) { - return $elm.attr('type') === 'checkbox' ? - ensureArray(value) : - value; + return $elm.attr('type') === 'checkbox' ? ensureArray(value) : value; } function Filter(elm) { @@ -20,7 +18,7 @@ function Filter(elm) { this.$input = this.$elm.find('input:not([name^="_"])'); this.$filterLabel = this.$elm.closest('.accordion__content').prev(); // on error message, click to open feedback panel - this.$elm.on('click', '.js-filter-feedback', function () { + this.$elm.on('click', '.js-filter-feedback', function() { $(document.body).trigger('feedback:open'); }); @@ -51,9 +49,9 @@ Filter.prototype.fromQuery = function(query) { }; Filter.prototype.setValue = function(value) { - var $input = this.$input.data('temp') ? - this.$elm.find('#' + this.$input.data('temp')) : - this.$input; + var $input = this.$input.data('temp') + ? this.$elm.find('#' + this.$input.data('temp')) + : this.$input; $input.val(prepareValue($input, value)).change(); return this; }; @@ -72,7 +70,9 @@ Filter.prototype.formatValue = function($input, value) { }; Filter.prototype.handleAddEvent = function(e, opts) { - if (opts.name !== this.name) { return; } + if (opts.name !== this.name) { + return; + } // The only time when opts.filterLabel != this.$filterLabel // is when a checkbox is a subfilter of a multifilter. // In that case, the multifilter explicitly sets the label and the checkbox @@ -86,7 +86,9 @@ Filter.prototype.handleAddEvent = function(e, opts) { Filter.prototype.handleRemoveEvent = function(e, opts) { // Don't decrement on initial page load - if (opts.name !== this.name || opts.loadedOnce !== true) { return; } + if (opts.name !== this.name || opts.loadedOnce !== true) { + return; + } var $filterLabel = opts.filterLabel || this.$filterLabel; this.decrement($filterLabel); this.setLastAction(e, opts); @@ -96,8 +98,7 @@ Filter.prototype.increment = function($filterLabel) { var filterCount = $filterLabel.find('.filter-count'); if (filterCount.html()) { filterCount.html(parseInt(filterCount.html(), 10) + 1); - } - else { + } else { $filterLabel.append(' <span class="filter-count">1</span>'); } }; @@ -106,14 +107,15 @@ Filter.prototype.decrement = function($filterLabel) { var filterCount = $filterLabel.find('.filter-count'); if (filterCount.html() === '1') { filterCount.remove(); - } - else { + } else { filterCount.html(parseInt(filterCount.html(), 10) - 1); } }; Filter.prototype.setLastAction = function(e, opts) { - if (opts.name !== this.name) { return; } + if (opts.name !== this.name) { + return; + } if (e.type === 'filter:added') { this.lastAction = 'Filter added'; @@ -143,8 +145,8 @@ Filter.prototype.enable = function() { var $this = $(this); $this.removeClass('is-disabled').prop('disabled', false); $this.trigger('filter:enabled', { - key: $this.attr('id') - }); + key: $this.attr('id') + }); }); this.isEnabled = true; }; @@ -154,4 +156,3 @@ module.exports = { ensureArray: ensureArray, prepareValue: prepareValue }; - diff --git a/fec/fec/static/js/modules/filters/filter-control.js b/fec/fec/static/js/modules/filters/filter-control.js index 8c0969ee81..4f929b426e 100644 --- a/fec/fec/static/js/modules/filters/filter-control.js +++ b/fec/fec/static/js/modules/filters/filter-control.js @@ -42,4 +42,4 @@ FilterControl.prototype.handleChange = function() { ]); }; -module.exports = {FilterControl: FilterControl}; +module.exports = { FilterControl: FilterControl }; diff --git a/fec/fec/static/js/modules/filters/filter-panel.js b/fec/fec/static/js/modules/filters/filter-panel.js index 35acc540f1..5024f502d7 100644 --- a/fec/fec/static/js/modules/filters/filter-panel.js +++ b/fec/fec/static/js/modules/filters/filter-panel.js @@ -56,13 +56,16 @@ FilterPanel.prototype.show = function(focus) { // Don't focus on the first filter unless explicitly intended to // Prevents the first filter from being focused on initial page load if (focus) { - this.$body.find('input, select, button:not(.js-filter-close)').first().focus(); + this.$body + .find('input, select, button:not(.js-filter-close)') + .first() + .focus(); } }; FilterPanel.prototype.hide = function() { if (!helpers.isLargeScreen()) { - var top = this.$toggle.outerHeight() + this.$toggle.position().top; + var top = this.$toggle.outerHeight() + this.$toggle.position().top; this.$content.css('top', top); } this.$body.removeClass('is-open'); @@ -83,29 +86,31 @@ FilterPanel.prototype.toggle = function() { FilterPanel.prototype.handleAddEvent = function(e, opts) { // If it's a data-type toggle, we tell it to ignore for the count of active filters - if (opts.ignoreCount) { return; } + if (opts.ignoreCount) { + return; + } var filterCount = this.$filterHeader.find('.filter-count'); if (filterCount.html()) { filterCount.html(parseInt(filterCount.html(), 10) + 1); - } - else { + } else { this.$filterHeader.append(' <span class="filter-count">1</span>'); } }; FilterPanel.prototype.handleRemoveEvent = function(e, opts) { - if (opts.loadedOnce !== true) { return; } + if (opts.loadedOnce !== true) { + return; + } var filterCount = this.$filterHeader.find('.filter-count'); if (filterCount.html() === '1') { filterCount.remove(); - } - else { + } else { filterCount.html(parseInt(filterCount.html(), 10) - 1); } }; -module.exports = {FilterPanel: FilterPanel}; +module.exports = { FilterPanel: FilterPanel }; diff --git a/fec/fec/static/js/modules/filters/filter-set.js b/fec/fec/static/js/modules/filters/filter-set.js index f2fd81c1ef..9581786545 100644 --- a/fec/fec/static/js/modules/filters/filter-set.js +++ b/fec/fec/static/js/modules/filters/filter-set.js @@ -30,15 +30,15 @@ function FilterSet(elm) { } var filterMap = { - 'text': TextFilter, - 'checkbox': CheckboxFilter, - 'date': DateFilter, - 'typeahead': TypeaheadFilter, - 'election': ElectionFilter, - 'multi': MultiFilter, - 'select': SelectFilter, - 'toggle': ToggleFilter, - 'range': RangeFilter, + text: TextFilter, + checkbox: CheckboxFilter, + date: DateFilter, + typeahead: TypeaheadFilter, + election: ElectionFilter, + multi: MultiFilter, + select: SelectFilter, + toggle: ToggleFilter, + range: RangeFilter }; FilterSet.prototype.buildFilter = function($elm) { @@ -49,7 +49,9 @@ FilterSet.prototype.buildFilter = function($elm) { FilterSet.prototype.activate = function($selector) { var self = this; - var query = helpers.sanitizeQueryParams(URI.parseQuery(window.location.search)); + var query = helpers.sanitizeQueryParams( + URI.parseQuery(window.location.search) + ); var filters = _.chain($selector) .map(function(elm) { var filter = self.buildFilter($(elm)); // .fromQuery(query); @@ -107,16 +109,20 @@ FilterSet.prototype.activateAll = function() { }; FilterSet.prototype.serialize = function() { - return _.reduce(this.$body.find('input,select').serializeArray(), function(memo, val) { - if (val.value && val.name.slice(0, 1) !== '_') { - if (memo[val.name]) { - memo[val.name].push(val.value); - } else{ - memo[val.name] = [val.value]; + return _.reduce( + this.$body.find('input,select').serializeArray(), + function(memo, val) { + if (val.value && val.name.slice(0, 1) !== '_') { + if (memo[val.name]) { + memo[val.name].push(val.value); + } else { + memo[val.name] = [val.value]; + } } - } - return memo; - }, {}); + return memo; + }, + {} + ); }; FilterSet.prototype.clear = function() { @@ -143,7 +149,8 @@ FilterSet.prototype.handleValidation = function(e, opts) { FilterSet.prototype.switchFilters = function(dataType) { // Identify which filter group to show and which to hide var currentFilters = '.js-' + dataType + '-filters'; - var otherFilters = dataType == 'efiling' ? '.js-processed-filters' : '.js-efiling-filters'; + var otherFilters = + dataType == 'efiling' ? '.js-processed-filters' : '.js-efiling-filters'; // Toggle visibility of filters this.$body.find(otherFilters).attr('aria-hidden', true); @@ -161,10 +168,12 @@ FilterSet.prototype.switchFilters = function(dataType) { FilterSet.prototype.activateSwitchedFilters = function(dataType) { // Save the current query for later - var query = helpers.sanitizeQueryParams(URI.parseQuery(window.location.search)); + var query = helpers.sanitizeQueryParams( + URI.parseQuery(window.location.search) + ); // Set forceRemove: true to clear date filters that are usually nonremovable - this.$body.trigger('tag:removeAll', {forceRemove: true}); + this.$body.trigger('tag:removeAll', { forceRemove: true }); // Go through the current panel and set loaded-once on each input // So that they don't show loading indicators _.each(this.filters, function(filter) { @@ -178,7 +187,8 @@ FilterSet.prototype.activateSwitchedFilters = function(dataType) { } // Identify which set of filters to activate and store as this.filters - this.filters = dataType === 'efiling' ? this.efilingFilters : this.processedFilters; + this.filters = + dataType === 'efiling' ? this.efilingFilters : this.processedFilters; _.each(this.filters, function(filter) { filter.fromQuery(query); @@ -188,4 +198,4 @@ FilterSet.prototype.activateSwitchedFilters = function(dataType) { this.firstLoad = false; }; -module.exports = {FilterSet: FilterSet}; +module.exports = { FilterSet: FilterSet }; diff --git a/fec/fec/static/js/modules/filters/filter-tags.js b/fec/fec/static/js/modules/filters/filter-tags.js index fe4544b456..92da85799a 100644 --- a/fec/fec/static/js/modules/filters/filter-tags.js +++ b/fec/fec/static/js/modules/filters/filter-tags.js @@ -6,38 +6,39 @@ var _ = require('underscore'); var BODY_TEMPLATE = _.template( '<div>' + '<div class="row">' + - '<h3 class="tags__title">Viewing ' + - '<span class="js-count" aria-hidden="true"></span> ' + - '<span class="js-result-type">filtered {{ resultType }} for:</span>' + - '</h3>' + - '<button type="button" class="js-filter-clear button--unstyled tags__clear" aria-hidden="true">Clear all filters</button>' + + '<h3 class="tags__title">Viewing ' + + '<span class="js-count" aria-hidden="true"></span> ' + + '<span class="js-result-type">filtered {{ resultType }} for:</span>' + + '</h3>' + + '<button type="button" class="js-filter-clear button--unstyled tags__clear" aria-hidden="true">Clear all filters</button>' + '</div>' + '<ul class="tags">' + '</ul>' + - '</div>', - {interpolate: /\{\{(.+?)\}\}/g} + '</div>', + { interpolate: /\{\{(.+?)\}\}/g } ); var TAG_TEMPLATE = _.template( '<div data-id="{{ key }}" data-removable="true" class="tag__item">' + '{{ value }}' + '<button class="button js-close tag__remove">' + - '<span class="u-visually-hidden">Remove</span>' + + '<span class="u-visually-hidden">Remove</span>' + '</button>' + - '</div>', - {interpolate: /\{\{(.+?)\}\}/g} + '</div>', + { interpolate: /\{\{(.+?)\}\}/g } ); var NONREMOVABLE_TAG_TEMPLATE = _.template( '<div data-id="{{ key }}" data-removable="false" data-remove-on-switch="{{ removeOnSwitch }}" class="tag__item">' + '{{ value }}' + - '</div>', {interpolate: /\{\{(.+?)\}\}/g} + '</div>', + { interpolate: /\{\{(.+?)\}\}/g } ); function TagList(opts) { this.opts = opts; - this.$body = $(BODY_TEMPLATE({resultType: this.opts.resultType})); + this.$body = $(BODY_TEMPLATE({ resultType: this.opts.resultType })); this.$list = this.$body.find('.tags'); this.$resultType = this.$body.find('.js-result-type'); this.$clear = this.$body.find('.js-filter-clear'); @@ -59,16 +60,23 @@ function TagList(opts) { } TagList.prototype.addTag = function(e, opts) { - var tag = opts.nonremovable ? NONREMOVABLE_TAG_TEMPLATE(opts) : TAG_TEMPLATE(opts); + var tag = opts.nonremovable + ? NONREMOVABLE_TAG_TEMPLATE(opts) + : TAG_TEMPLATE(opts); var name = opts.range ? opts.rangeName : opts.name; var $tagCategory = this.$list.find('[data-tag-category="' + name + '"]'); this.removeTag(opts.key, false); if ($tagCategory.length > 0) { this.addTagItem($tagCategory, tag, opts); - } - else { - this.$list.append('<li data-tag-category="' + name + '" class="tag__category">' + tag + '</li>'); + } else { + this.$list.append( + '<li data-tag-category="' + + name + + '" class="tag__category">' + + tag + + '</li>' + ); } if (this.$list.find('.tag__item').length > 0) { @@ -86,11 +94,9 @@ TagList.prototype.addTagItem = function($tagCategory, tag, opts) { if (opts.range == 'min') { $tagCategory.addClass(rangeClass).prepend(tag); - } - else if (opts.range == 'max') { + } else if (opts.range == 'max') { $tagCategory.addClass(rangeClass).append(tag); - } - else { + } else { $tagCategory.append(tag); } }; @@ -100,10 +106,12 @@ TagList.prototype.removeTagElement = function($tag, emit) { var $tagCategory = $tag.parent(); var key = $tag.data('id'); if (emit) { - $tag.trigger('tag:removed', [{key: key}]); + $tag.trigger('tag:removed', [{ key: key }]); } $tag.remove(); - $tagCategory.removeClass('tag__category__range--amount tag__category__range--date'); + $tagCategory.removeClass( + 'tag__category__range--amount tag__category__range--date' + ); if ($tagCategory.is(':empty')) { $tagCategory.remove(); } @@ -138,14 +146,14 @@ TagList.prototype.removeTag = function(key, emit, forceRemove) { TagList.prototype.removeAllTags = function(e, opts, emit) { var self = this; var forceRemove = opts.forceRemove || false; - this.$list.find('[data-removable]').each(function(){ + this.$list.find('[data-removable]').each(function() { self.removeTag($(this).data('id'), true, forceRemove); }); // Don't emit another event unless told to do so // This way it can be triggered as an event listener without creating more if (emit) { - $(document.body).trigger('tag:removeAll', {removeAll: false}); + $(document.body).trigger('tag:removeAll', { removeAll: false }); } }; @@ -154,12 +162,16 @@ TagList.prototype.removeTagEvt = function(e, opts) { }; TagList.prototype.removeTagDom = function(e) { - var key = $(e.target).closest('.tag__item').data('id'); + var key = $(e.target) + .closest('.tag__item') + .data('id'); this.removeTag(key, true); }; TagList.prototype.renameTag = function(e, opts) { - var tag = opts.nonremovable ? NONREMOVABLE_TAG_TEMPLATE(opts) : TAG_TEMPLATE(opts); + var tag = opts.nonremovable + ? NONREMOVABLE_TAG_TEMPLATE(opts) + : TAG_TEMPLATE(opts); var $tag = this.$list.find('[data-id="' + opts.key + '"]'); if ($tag.length) { $tag.replaceWith(tag); @@ -176,4 +188,4 @@ TagList.prototype.enableTag = function(e, opts) { $tag.closest('.tag__category').show(); }; -module.exports = {TagList: TagList}; +module.exports = { TagList: TagList }; diff --git a/fec/fec/static/js/modules/filters/filter-typeahead.js b/fec/fec/static/js/modules/filters/filter-typeahead.js index 7054320d92..ea8526b9a7 100644 --- a/fec/fec/static/js/modules/filters/filter-typeahead.js +++ b/fec/fec/static/js/modules/filters/filter-typeahead.js @@ -18,9 +18,9 @@ function slugify(value) { } function formatLabel(datum) { - return datum.name ? - datum.name + ' (' + datum.id + ')' : - '"' + stripQuotes(datum.id) + '"'; + return datum.name + ? datum.name + ' (' + datum.id + ')' + : '"' + stripQuotes(datum.id) + '"'; } function formatId(value) { @@ -31,11 +31,10 @@ function stripQuotes(value) { return value.replace(/["]+/g, ''); } - var textDataset = { display: 'id', source: function(query, syncResults) { - syncResults([{id: helpers.sanitizeValue(query)}]); + syncResults([{ id: helpers.sanitizeValue(query) }]); }, templates: { suggestion: function(datum) { @@ -55,7 +54,11 @@ var FilterTypeahead = function(selector, dataset, allowText) { this.$selected = this.$elm.find('.dropdown__selected'); this.$elm.on('change', 'input[type="text"]', this.handleChange.bind(this)); - this.$elm.on('change', 'input[type="checkbox"]', this.handleCheckbox.bind(this)); + this.$elm.on( + 'change', + 'input[type="checkbox"]', + this.handleCheckbox.bind(this) + ); this.$elm.on('click', '.dropdown__remove', this.removeCheckbox.bind(this)); this.$elm.on('mouseenter', '.tt-suggestion', this.handleHover.bind(this)); @@ -75,7 +78,7 @@ var FilterTypeahead = function(selector, dataset, allowText) { }; FilterTypeahead.prototype.typeaheadInit = function() { - var opts = {minLength: 3, hint: false, highlight: true}; + var opts = { minLength: 3, hint: false, highlight: true }; if (this.allowText && this.dataset) { this.$field.typeahead(opts, textDataset, this.dataset); } else if (this.allowText && !this.dataset) { @@ -125,10 +128,15 @@ FilterTypeahead.prototype.handleKeypress = function(e) { }; FilterTypeahead.prototype.handleChange = function() { - if ((this.allowText && this.$field.typeahead('val').length > 1) || this.datum) { + if ( + (this.allowText && this.$field.typeahead('val').length > 1) || + this.datum + ) { this.enableButton(); - } else if (this.$field.typeahead('val').length === 0 || - (!this.allowText && this.$field.typeahead('val').length < 3)) { + } else if ( + this.$field.typeahead('val').length === 0 || + (!this.allowText && this.$field.typeahead('val').length < 3) + ) { this.datum = null; this.disableButton(); } @@ -172,7 +180,7 @@ FilterTypeahead.prototype.handleSubmit = function(e) { } else if (!this.datum && !this.allowText) { this.handleSelect(e, this.firstItem); } else if (this.allowText && this.$field.typeahead('val').length > 0) { - this.handleSelect(e, {id: this.$field.typeahead('val')}); + this.handleSelect(e, { id: this.$field.typeahead('val') }); } }; @@ -183,29 +191,35 @@ FilterTypeahead.prototype.clearInput = function() { FilterTypeahead.prototype.enableButton = function() { this.searchEnabled = true; - this.$button.removeClass('is-disabled').attr('tabindex', '1').attr('disabled', false); + this.$button + .removeClass('is-disabled') + .attr('tabindex', '1') + .attr('disabled', false); }; FilterTypeahead.prototype.disableButton = function() { this.searchEnabled = false; - this.$button.addClass('is-disabled').attr('tabindex', '-1').attr('disabled', false); + this.$button + .addClass('is-disabled') + .attr('tabindex', '-1') + .attr('disabled', false); }; FilterTypeahead.prototype.checkboxTemplate = _.template( '<li>' + '<input ' + - 'id="{{id}}" ' + - 'name="{{name}}" ' + - 'value="{{value}}" ' + - 'type="checkbox" ' + - 'checked' + + 'id="{{id}}" ' + + 'name="{{name}}" ' + + 'value="{{value}}" ' + + 'type="checkbox" ' + + 'checked' + '/>' + '<label for="{{id}}">{{label}}</label>' + '<button class="dropdown__remove">' + - '<span class="u-visually-hidden">Remove</span>' + + '<span class="u-visually-hidden">Remove</span>' + '</button>' + - '</li>', - {interpolate: /\{\{(.+?)\}\}/g} + '</li>', + { interpolate: /\{\{(.+?)\}\}/g } ); FilterTypeahead.prototype.appendCheckbox = function(opts) { @@ -266,13 +280,19 @@ FilterTypeahead.prototype.updateFilters = function(response) { var idKey = this.dataset.name + '_id'; response.results.forEach(function(result) { var label = result.name + ' (' + result[idKey] + ')'; - self.$elm.find('label[for="' + self.fieldName + '-' + result[idKey] + '-checkbox"]').text(label); - self.$elm.find('#' + self.fieldName + '-' + result[idKey] + '-checkbox').trigger('filter:renamed', [ - { - key: self.fieldName + '-' + result[idKey] + '-checkbox', - value: label - } - ]); + self.$elm + .find( + 'label[for="' + self.fieldName + '-' + result[idKey] + '-checkbox"]' + ) + .text(label); + self.$elm + .find('#' + self.fieldName + '-' + result[idKey] + '-checkbox') + .trigger('filter:renamed', [ + { + key: self.fieldName + '-' + result[idKey] + '-checkbox', + value: label + } + ]); }); } }; @@ -285,7 +305,10 @@ FilterTypeahead.prototype.changeDataset = function(e, opts) { this.$field.typeahead('destroy'); // If the value array is only individuals and not committees // set the dataset to empty and re-init - if (opts.filterValue.indexOf('individual') > -1 && opts.filterValue.indexOf('committee') < 0) { + if ( + opts.filterValue.indexOf('individual') > -1 && + opts.filterValue.indexOf('committee') < 0 + ) { this.dataset = null; this.allowText = true; this.typeaheadInit(); @@ -297,4 +320,4 @@ FilterTypeahead.prototype.changeDataset = function(e, opts) { } }; -module.exports = {FilterTypeahead: FilterTypeahead}; +module.exports = { FilterTypeahead: FilterTypeahead }; diff --git a/fec/fec/static/js/modules/filters/multi-filter.js b/fec/fec/static/js/modules/filters/multi-filter.js index ddd9fa6e48..cac8df93be 100644 --- a/fec/fec/static/js/modules/filters/multi-filter.js +++ b/fec/fec/static/js/modules/filters/multi-filter.js @@ -19,13 +19,15 @@ MultiFilter.constructor = MultiFilter; MultiFilter.prototype.activateSubfilters = function() { var subfilters = []; // Activate each sub-filter and add it to an array - this.$elm.find('.js-sub-filter[data-name="' + this.name + '"]').each(function() { - var subfilter = new CheckboxFilter(this); - // Explicitly assign filterLabel, which will show the count - // Necessary because each subfilter may be part of a different accordion - subfilter.$filterLabel = $('#' + subfilter.$elm.data('filter-label')); - subfilters.push(subfilter); - }); + this.$elm + .find('.js-sub-filter[data-name="' + this.name + '"]') + .each(function() { + var subfilter = new CheckboxFilter(this); + // Explicitly assign filterLabel, which will show the count + // Necessary because each subfilter may be part of a different accordion + subfilter.$filterLabel = $('#' + subfilter.$elm.data('filter-label')); + subfilters.push(subfilter); + }); return subfilters; }; diff --git a/fec/fec/static/js/modules/filters/range-filter.js b/fec/fec/static/js/modules/filters/range-filter.js index 2a69d46966..f25329cd00 100644 --- a/fec/fec/static/js/modules/filters/range-filter.js +++ b/fec/fec/static/js/modules/filters/range-filter.js @@ -64,4 +64,4 @@ RangeFilter.prototype.handleKeyup = function() { this.$submit.removeClass('is-disabled'); }; -module.exports = {RangeFilter: RangeFilter}; +module.exports = { RangeFilter: RangeFilter }; diff --git a/fec/fec/static/js/modules/filters/select-filter.js b/fec/fec/static/js/modules/filters/select-filter.js index 09e613a042..769f40cf1f 100644 --- a/fec/fec/static/js/modules/filters/select-filter.js +++ b/fec/fec/static/js/modules/filters/select-filter.js @@ -35,4 +35,4 @@ SelectFilter.prototype.setValue = function(value) { this.$input.change(); }; -module.exports = {SelectFilter: SelectFilter}; +module.exports = { SelectFilter: SelectFilter }; diff --git a/fec/fec/static/js/modules/filters/text-filter.js b/fec/fec/static/js/modules/filters/text-filter.js index 0f44659c38..14d969e95c 100644 --- a/fec/fec/static/js/modules/filters/text-filter.js +++ b/fec/fec/static/js/modules/filters/text-filter.js @@ -75,24 +75,24 @@ TextFilter.prototype.handleBlur = function() { TextFilter.prototype.checkboxTemplate = _.template( '<li>' + '<input ' + - 'id="{{id}}" ' + - 'name="{{name}}" ' + - 'value="{{value}}" ' + - 'type="checkbox" ' + - 'checked' + + 'id="{{id}}" ' + + 'name="{{name}}" ' + + 'value="{{value}}" ' + + 'type="checkbox" ' + + 'checked' + '/>' + '<label for="{{id}}">"{{value}}"</label>' + '<button class="dropdown__remove js-remove">' + - '<span class="u-visually-hidden">Remove</span>' + + '<span class="u-visually-hidden">Remove</span>' + '</button>' + - '</li>', - {interpolate: /\{\{(.+?)\}\}/g} + '</li>', + { interpolate: /\{\{(.+?)\}\}/g } ); // Remove the event handlers for adding and removing tags // So the filter count doesn't count double for the text filter and checkbox -TextFilter.prototype.handleAddEvent = function(){}; -TextFilter.prototype.handleRemoveEvent = function(){}; +TextFilter.prototype.handleAddEvent = function() {}; +TextFilter.prototype.handleRemoveEvent = function() {}; TextFilter.prototype.appendCheckbox = function(value) { if (!this.checkboxList) { @@ -111,10 +111,12 @@ TextFilter.prototype.appendCheckbox = function(value) { }; TextFilter.prototype.appendCheckboxList = function() { - var $checkboxes = $('<ul class="js-filter dropdown__selected" data-filter="checkbox" data-removable="true"></ul>'); + var $checkboxes = $( + '<ul class="js-filter dropdown__selected" data-filter="checkbox" data-removable="true"></ul>' + ); this.$elm.find('label').after($checkboxes); this.checkboxList = new CheckboxFilter($checkboxes); this.checkboxList.name = this.name; }; -module.exports = {TextFilter: TextFilter}; +module.exports = { TextFilter: TextFilter }; diff --git a/fec/fec/static/js/modules/filters/toggle-filter.js b/fec/fec/static/js/modules/filters/toggle-filter.js index 030794fc5b..60c29b3fda 100644 --- a/fec/fec/static/js/modules/filters/toggle-filter.js +++ b/fec/fec/static/js/modules/filters/toggle-filter.js @@ -18,7 +18,10 @@ ToggleFilter.prototype = Object.create(Filter.prototype); ToggleFilter.constructor = ToggleFilter; ToggleFilter.prototype.fromQuery = function(query) { - this.$elm.find('input[value="' + query[this.name] + '"]').prop('checked', true).change(); + this.$elm + .find('input[value="' + query[this.name] + '"]') + .prop('checked', true) + .change(); }; ToggleFilter.prototype.handleChange = function(e) { @@ -44,8 +47,8 @@ ToggleFilter.prototype.setInitialValue = function() { // If a toggle is checked by default in the DOM, call handleChange() var $checked = this.$elm.find('input:checked'); if ($checked.length > 0) { - this.handleChange({target: $checked}); + this.handleChange({ target: $checked }); } }; -module.exports = {ToggleFilter: ToggleFilter}; +module.exports = { ToggleFilter: ToggleFilter }; diff --git a/fec/fec/static/js/modules/filters/typeahead-filter.js b/fec/fec/static/js/modules/filters/typeahead-filter.js index c49e1acba2..84f097725c 100644 --- a/fec/fec/static/js/modules/filters/typeahead-filter.js +++ b/fec/fec/static/js/modules/filters/typeahead-filter.js @@ -13,7 +13,11 @@ function TypeaheadFilter(elm) { var allowText = this.$elm.data('allow-text') !== undefined; var dataset = key ? typeahead.datasets[key] : null; this.typeaheadFilter = new FilterTypeahead(this.$elm, dataset, allowText); - this.typeaheadFilter.$elm.on('change', 'input[type="checkbox"]', this.handleNestedChange.bind(this)); + this.typeaheadFilter.$elm.on( + 'change', + 'input[type="checkbox"]', + this.handleNestedChange.bind(this) + ); } TypeaheadFilter.prototype = Object.create(Filter.Filter.prototype); @@ -47,7 +51,10 @@ TypeaheadFilter.prototype.handleNestedChange = function(e) { }; TypeaheadFilter.prototype.disable = function() { - this.$elm.find('input, label, button').addClass('is-disabled').prop('disabled', true); + this.$elm + .find('input, label, button') + .addClass('is-disabled') + .prop('disabled', true); this.$elm.find('input:checked').each(function() { $(this).trigger('filter:disabled', { key: $(this).attr('id') @@ -56,7 +63,10 @@ TypeaheadFilter.prototype.disable = function() { }; TypeaheadFilter.prototype.enable = function() { - this.$elm.find('input, label, button').removeClass('is-disabled').prop('disabled', false); + this.$elm + .find('input, label, button') + .removeClass('is-disabled') + .prop('disabled', false); this.$elm.find('input:checked').each(function() { $(this).trigger('filter:enabled', { key: $(this).attr('id') @@ -64,4 +74,4 @@ TypeaheadFilter.prototype.enable = function() { }); }; -module.exports = {TypeaheadFilter: TypeaheadFilter}; +module.exports = { TypeaheadFilter: TypeaheadFilter }; diff --git a/fec/fec/static/js/modules/filters/validate-date-filters.js b/fec/fec/static/js/modules/filters/validate-date-filters.js index c1e7bbd193..c07e400e5a 100644 --- a/fec/fec/static/js/modules/filters/validate-date-filters.js +++ b/fec/fec/static/js/modules/filters/validate-date-filters.js @@ -85,18 +85,18 @@ ValidateDateFilter.prototype.validate = function() { var minDate = moment(this.$minDate.val(), 'MM/DD/YYYY'); var maxDate = moment(this.$maxDate.val(), 'MM/DD/YYYY'); var span = maxDate.diff(minDate, 'years', true); - if ( span <= this.duration ) { + if (span <= this.duration) { this.hideWarning(); this.$elm.trigger('filters:validation', [ { - isValid: true, + isValid: true } ]); } else { this.showWarning(); this.$elm.trigger('filters:validation', [ { - isValid: false, + isValid: false } ]); } @@ -106,9 +106,13 @@ ValidateDateFilter.prototype.fromQuery = function(query) { // If no values are passed in the query, then default to today - Jan 1 from last year var now = moment().format('MM/DD/YYYY'); var startYear = moment().format('YYYY') - 1; - var defaultStart = moment('01/01/' + startYear, 'MM-DD-YYYY').format('MM/DD/YYYY'); + var defaultStart = moment('01/01/' + startYear, 'MM-DD-YYYY').format( + 'MM/DD/YYYY' + ); - var minDate = query['min_' + this.name] ? query['min_' + this.name] : defaultStart; + var minDate = query['min_' + this.name] + ? query['min_' + this.name] + : defaultStart; var maxDate = query['max_' + this.name] ? query['max_' + this.name] : now; this.$minDate.val(minDate).change(); this.$maxDate.val(maxDate).change(); @@ -123,22 +127,22 @@ ValidateDateFilter.prototype.handleRemoveAll = function(e, opts) { function remove($filter) { $filter.val(''); $filter.data('had-value', false); - $filter.trigger('filter:removed', {loadedOnce: true}); + $filter.trigger('filter:removed', { loadedOnce: true }); } if (forceRemove) { - remove(this.$minDate); - remove(this.$maxDate); + remove(this.$minDate); + remove(this.$maxDate); } }; ValidateDateFilter.prototype.showWarning = function() { if (!this.showingWarning) { var warning = - '<div class="filter__message filter__message--error">' + + '<div class="filter__message filter__message--error">' + '<strong>Time period is too broad</strong><br>' + 'Please enter dates within six years of each other.' + - '</div>'; + '</div>'; this.$range.after(warning); this.showingWarning = true; } @@ -151,4 +155,4 @@ ValidateDateFilter.prototype.hideWarning = function() { } }; -module.exports = {ValidateDateFilter: ValidateDateFilter}; +module.exports = { ValidateDateFilter: ValidateDateFilter }; diff --git a/fec/fec/static/js/modules/fips.js b/fec/fec/static/js/modules/fips.js index 1ab660655c..4081751e08 100644 --- a/fec/fec/static/js/modules/fips.js +++ b/fec/fec/static/js/modules/fips.js @@ -5,15 +5,20 @@ var _ = require('underscore'); function byField(values, key) { - var getter = typeof key === 'function' ? - key : - function(val) { - return val[key]; - }; - return _.reduce(values, function(acc, val) { - acc[getter(val)] = val; - return acc; - }, {}); + var getter = + typeof key === 'function' + ? key + : function(val) { + return val[key]; + }; + return _.reduce( + values, + function(acc, val) { + acc[getter(val)] = val; + return acc; + }, + {} + ); } var fips = _.each(require('../data/state.json'), function(row) { diff --git a/fec/fec/static/js/modules/form-nav.js b/fec/fec/static/js/modules/form-nav.js index 4cd06962e9..5cad7a7d65 100644 --- a/fec/fec/static/js/modules/form-nav.js +++ b/fec/fec/static/js/modules/form-nav.js @@ -12,9 +12,9 @@ function FormNav(form) { FormNav.prototype.handleChange = function() { var allSelects = this.form.querySelectorAll('select,input'); // Remove names from all selects with no values - for(var i = 0; i < allSelects.length; i++) { + for (var i = 0; i < allSelects.length; i++) { var select = allSelects[i]; - if(select.getAttribute('name') && !select.value) { + if (select.getAttribute('name') && !select.value) { select.setAttribute('name', ''); } } @@ -22,4 +22,4 @@ FormNav.prototype.handleChange = function() { this.form.submit(); }; -module.exports = {FormNav: FormNav}; +module.exports = { FormNav: FormNav }; diff --git a/fec/fec/static/js/modules/helpers.js b/fec/fec/static/js/modules/helpers.js index 34c16675f3..349f32cc18 100644 --- a/fec/fec/static/js/modules/helpers.js +++ b/fec/fec/static/js/modules/helpers.js @@ -37,7 +37,7 @@ var formatMap = { function anchorify(attr) { // Attach anchor <a> links to any tag with a given attribute - $('['+attr+']').each(function(idx, item) { + $('[' + attr + ']').each(function(idx, item) { var elt = $(item); var link = $('<a></a>'); var href = '#' + elt.attr('id'); @@ -48,14 +48,14 @@ function anchorify(attr) { }); } -function scrollAnchor(ms){ - ms = ms || 1000 - if(window.location.hash) { - setTimeout( function(){ +function scrollAnchor(ms) { + ms = ms || 1000; + if (window.location.hash) { + setTimeout(function() { $('html, body').animate({ - scrollTop : $(window.location.hash).offset().top - }) - }, ms) + scrollTop: $(window.location.hash).offset().top + }); + }, ms); } } @@ -110,23 +110,22 @@ var numberFormatter = function(number) { Handlebars.registerHelper('formatNumber', numberFormatter); Handlebars.registerHelper({ - eq: function (v1, v2) { + eq: function(v1, v2) { return v1 === v2; }, toUpperCase: function(value) { - return value.substr(0,1).toUpperCase() + value.substr(1); + return value.substr(0, 1).toUpperCase() + value.substr(1); } }); var globals = { EARMARKED_CODES: ['15E', '24I', '24T'] -} +}; Handlebars.registerHelper('isEarmarked', function(receipt_type) { if (globals.EARMARKED_CODES.indexOf(receipt_type) > -1) { - return true; - } - else { + return true; + } else { return false; } }); @@ -178,16 +177,18 @@ Handlebars.registerHelper('basePath', BASE_PATH); Handlebars.registerHelper('panelRow', function(label, options) { return new Handlebars.SafeString( '<tr>' + - '<td class="panel__term">' + label + '</td>' + - '<td class="panel__data">' + options.fn(this) + '</td>' + - '</tr>' + '<td class="panel__term">' + + label + + '</td>' + + '<td class="panel__data">' + + options.fn(this) + + '</td>' + + '</tr>' ); }); Handlebars.registerHelper('entityUrl', function(entity, options) { - var query, - id, - url; + var query, id, url; if (options.hash.query) { query = { cycle: options.hash.query.cycle || null, @@ -224,8 +225,10 @@ Handlebars.registerHelper('convertBoolean', function(bool) { }); Handlebars.registerHelper('format_range', function(year) { - var firstYear = Number(year) - 1; - return new Handlebars.SafeString(firstYear.toString() + '–' + year.toString()); + var firstYear = Number(year) - 1; + return new Handlebars.SafeString( + firstYear.toString() + '–' + year.toString() + ); }); /** @@ -234,7 +237,7 @@ Handlebars.registerHelper('format_range', function(year) { **/ function formatCycleRange(year, duration) { // Year and duration is requred, if not provided return null - if( year == null || duration == null) { + if (year == null || duration == null) { return null; } var firstYear = Number(year) - duration + 1; @@ -272,7 +275,7 @@ function buildAppUrl(path, query) { function buildUrl(path, query) { return URI(API_LOCATION) .path(Array.prototype.concat(API_VERSION, path, '').join('/')) - .addQuery({api_key: API_KEY}) + .addQuery({ api_key: API_KEY }) .addQuery(query) .toString(); } @@ -280,12 +283,12 @@ function buildUrl(path, query) { function buildTableQuery(context, perPage) { var pageLength = pageLength || 0; var query = _.chain(context) - .pairs() - .filter(function(pair) { - return pair[1]; - }) - .object() - .value(); + .pairs() + .filter(function(pair) { + return pair[1]; + }) + .object() + .value(); return _.extend(query, { per_page: pageLength, @@ -300,8 +303,8 @@ function getTimePeriod(electionYear, cycle, electionFull, office) { H: 1 }; var min, - max, - duration = durations[office]; + max, + duration = durations[office]; if (electionFull) { min = parseInt(electionYear) - duration; @@ -327,30 +330,32 @@ function getTimePeriod(electionYear, cycle, electionFull, office) { function zeroPad(container, item, appendee) { // Subtract 2 so if it's close we don't go over var maxWidth = $(container).width() - 6; - $(container).find(appendee).empty(); - $(container).find(item).each(function() { - var itemWidth = $(this).width(); - // $appendee is where the period will be appended to - // You can pass either a child element of item or else it will be appended - // to item itself - var $appendee = appendee ? $(this).find(appendee) : $(this); - var value = $appendee.text(); - while ( itemWidth < maxWidth) { - value = '.' + value; - $appendee.text(value); - itemWidth = $(this).width(); - } - }); + $(container) + .find(appendee) + .empty(); + $(container) + .find(item) + .each(function() { + var itemWidth = $(this).width(); + // $appendee is where the period will be appended to + // You can pass either a child element of item or else it will be appended + // to item itself + var $appendee = appendee ? $(this).find(appendee) : $(this); + var value = $appendee.text(); + while (itemWidth < maxWidth) { + value = '.' + value; + $appendee.text(value); + itemWidth = $(this).width(); + } + }); } function amendmentVersion(most_recent) { if (most_recent === true) { return '<i class="icon-circle--check-outline--inline--left"></i>Current version'; - } - else if (most_recent === false) { + } else if (most_recent === false) { return '<i class="icon-circle--clock-reverse--inline--left"></i>Past version'; - } - else { + } else { return 'Version unknown'; } } @@ -363,7 +368,10 @@ function amendmentVersionDescription(row) { var amendment_num = 1; // because of messy data, do not show if not e-filing or null amendment indicator - if (row.means_filed !== API.means_filed_e_file || row.amendment_indicator === null) { + if ( + row.means_filed !== API.means_filed_e_file || + row.amendment_indicator === null + ) { return description; } @@ -400,7 +408,6 @@ function amendmentVersionDescription(row) { return description; } - function utcDate(dateString) { var originalDate = new Date(dateString); var date = originalDate.getUTCDate(); @@ -413,11 +420,14 @@ function missingDataReason(dataType) { // Returns a string explaining why data may not be showing // which is then used by the noData.hbs message var reasons = { - 'contributions': 'The committee has not received any contributions over $200', - 'disbursements': 'The committee has not made any disbursements', - 'independent-expenditures': 'No independent expenditures have been made in support or opposition of this candidate', - 'communication-costs': 'No communication costs have been made in support or opposition of this candidate', - 'electioneering': 'No electioneering communications have been made that mention this candidate', + contributions: 'The committee has not received any contributions over $200', + disbursements: 'The committee has not made any disbursements', + 'independent-expenditures': + 'No independent expenditures have been made in support or opposition of this candidate', + 'communication-costs': + 'No communication costs have been made in support or opposition of this candidate', + electioneering: + 'No electioneering communications have been made that mention this candidate', 'ie-made': 'The committee has not made any independent expenditures' }; @@ -441,16 +451,13 @@ function isInViewport($elm) { // Sanitizes a single value by removing HTML tags and whitelisting valid // characters. function sanitizeValue(value) { - var validCharactersRegEx = /[^a-z0-9-',.()\s]/ig; + var validCharactersRegEx = /[^a-z0-9-',.()\s]/gi; if (value !== null && value !== undefined) { if (_.isArray(value)) { for (var i = 0; i < value.length; i++) { if (value[i] !== null && value[i] !== undefined) { - value[i] = sanitize(value[i]).replace( - validCharactersRegEx, - '' - ); + value[i] = sanitize(value[i]).replace(validCharactersRegEx, ''); } } } else { @@ -481,7 +488,7 @@ function getCookie(name) { for (var i = 0; i < cookies.length; i++) { var cookie = $.trim(cookies[i]); // Does this cookie string begin with the name we want? - if (cookie.substring(0, name.length + 1) == (name + '=')) { + if (cookie.substring(0, name.length + 1) == name + '=') { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } diff --git a/fec/fec/static/js/modules/home-events.js b/fec/fec/static/js/modules/home-events.js index 1a86a2f6b7..669356f4c0 100644 --- a/fec/fec/static/js/modules/home-events.js +++ b/fec/fec/static/js/modules/home-events.js @@ -25,11 +25,11 @@ var updates = { // Home Page: Events and deadlines function HomepageEvents() { $.each(updates, function(eventClass, eventCategories) { - var url = calendarHelpers.getUrl('calendar-dates', - { 'sort': 'start_date', - 'min_start_date': todaysDate, - 'calendar_category_id': eventCategories - }); + var url = calendarHelpers.getUrl('calendar-dates', { + sort: 'start_date', + min_start_date: todaysDate, + calendar_category_id: eventCategories + }); $.getJSON(url).done(function(events) { var event = events.results[0]; @@ -37,16 +37,17 @@ function HomepageEvents() { if (typeof event !== 'undefined') { startDate = moment(event.start_date).format('MMMM D'); - } - else { + } else { event = ''; } - $(eventClass).html(eventsTemplate({ - startDate: startDate, - url: event.url, - summary: event.summary - })); + $(eventClass).html( + eventsTemplate({ + startDate: startDate, + url: event.url, + summary: event.summary + }) + ); }); }); } diff --git a/fec/fec/static/js/modules/keyword-modal.js b/fec/fec/static/js/modules/keyword-modal.js index 0443f34c5e..0a56b8c17c 100644 --- a/fec/fec/static/js/modules/keyword-modal.js +++ b/fec/fec/static/js/modules/keyword-modal.js @@ -20,17 +20,22 @@ function KeywordModal() { this.elm = document.querySelector('.js-keyword-modal'); this.$elm = $(this.elm); this.$form = this.$elm.find('form'); - this.$fields = this.$elm.find('#keywords-any, #keywords-all, #keywords-exact'); + this.$fields = this.$elm.find( + '#keywords-any, #keywords-all, #keywords-exact' + ); this.$excludeField = this.$elm.find('#keywords-none'); this.$submit = this.$elm.find('button[type="submit"]'); this.$submit.on('click', this.handleSubmit.bind(this)); this.dialog = new A11yDialog(this.elm); - this.$elm.on('dialog:show', function() { - $('body').css('overflow', 'hidden'); - this.fireEvent('Keyword modal: opened'); - }.bind(this)); + this.$elm.on( + 'dialog:show', + function() { + $('body').css('overflow', 'hidden'); + this.fireEvent('Keyword modal: opened'); + }.bind(this) + ); this.$elm.on('dialog:hide', function() { $('body').css('overflow', 'scroll'); @@ -51,7 +56,7 @@ KeywordModal.prototype.handleSubmit = function(e) { .addSearch('search', combinedValue); this.dialog.hide(); this.fireEvent('Keyword modal query: ' + combinedValue); - window.location = this.$form.attr("action") + query.toString(); + window.location = this.$form.attr('action') + query.toString(); }; /** @@ -89,14 +94,17 @@ KeywordModal.prototype.combineFields = function() { * @returns {string} The various words joined together with the correct operator */ KeywordModal.prototype.parseValue = function($input) { - var words = $input.val().replace(/"/g,'').split(' '); + var words = $input + .val() + .replace(/"/g, '') + .split(' '); var operator = $input.data('operator'); if (operator === 'and') { return words.join(' AND '); } else if (operator === 'or') { return words.join(' OR '); } else if (operator === 'exact') { - return '"' + $input.val().replace(/"/g,'') + '"'; + return '"' + $input.val().replace(/"/g, '') + '"'; } else if (operator === 'exclude') { return '-' + words.join(' -'); } diff --git a/fec/fec/static/js/modules/line-chart.js b/fec/fec/static/js/modules/line-chart.js index 088bfae4c5..9ebcefac61 100644 --- a/fec/fec/static/js/modules/line-chart.js +++ b/fec/fec/static/js/modules/line-chart.js @@ -11,7 +11,9 @@ var parseM = d3.time.format('%b'); var parseMY = d3.time.format('%b %Y'); var parseMDY = d3.time.format('%b %e, %Y'); -var bisectDate = d3.bisector(function(d) { return d.date; }).left; +var bisectDate = d3.bisector(function(d) { + return d.date; +}).left; var currentYear = new Date().getFullYear(); var MIN_CYCLE = 2008; @@ -36,7 +38,7 @@ function LineChart(selector, snapshot, dataType) { this.dataType = dataType; this.cycle = Number(DEFAULT_TIME_PERIOD); this.entityNames = ['candidate', 'party', 'pac']; - this.margin = {top: 10, right: 20, bottom: 30, left: 50}; + this.margin = { top: 10, right: 20, bottom: 30, left: 50 }; this.baseWidth = $(selector).width(); this.baseHeight = this.baseWidth * 0.5; this.height = this.baseHeight - this.margin.top - this.margin.bottom; @@ -63,10 +65,10 @@ function LineChart(selector, snapshot, dataType) { } LineChart.prototype.fetch = function(cycle) { - var entityTotalsURL = helpers.buildUrl( - ['totals', 'by_entity'], - { 'cycle': cycle, 'per_page': '100'} - ); + var entityTotalsURL = helpers.buildUrl(['totals', 'by_entity'], { + cycle: cycle, + per_page: '100' + }); $.getJSON(entityTotalsURL).done(this.handleResponse.bind(this)); }; @@ -90,21 +92,23 @@ LineChart.prototype.groupDataByType = function(results) { var datum; var date = helpers.utcDate(item.date); // If the data is in the future, it's probably wrong, so ignore it - if (date > today) { return; } + if (date > today) { + return; + } if (dataType === 'raised') { datum = { - 'date': date, - 'candidate': item.cumulative_candidate_receipts, - 'pac': item.cumulative_pac_receipts, - 'party': item.cumulative_party_receipts + date: date, + candidate: item.cumulative_candidate_receipts, + pac: item.cumulative_pac_receipts, + party: item.cumulative_party_receipts }; } else { datum = { - 'date': date, - 'candidate': item.cumulative_candidate_disbursements, - 'pac': item.cumulative_pac_disbursements, - 'party': item.cumulative_party_disbursements + date: date, + candidate: item.cumulative_candidate_disbursements, + pac: item.cumulative_pac_disbursements, + party: item.cumulative_party_disbursements }; } formattedData.push(datum); @@ -113,7 +117,7 @@ LineChart.prototype.groupDataByType = function(results) { this.chartData = _.sortBy(formattedData, 'date'); }; -LineChart.prototype.groupEntityTotals = function() { +LineChart.prototype.groupEntityTotals = function() { // Create separate arrays of data for each entity type // These will be used to draw the lines on the chart var chartData = this.chartData; @@ -132,9 +136,12 @@ LineChart.prototype.groupEntityTotals = function() { LineChart.prototype.setXScale = function() { // Set the x-scale to be from the first of the first year to the last day of the cycle - var x = d3.time.scale() - .domain([new Date('01/01/' + String(this.cycle - 1)), - new Date('12/31/' + String(this.cycle))]) + var x = d3.time + .scale() + .domain([ + new Date('01/01/' + String(this.cycle - 1)), + new Date('12/31/' + String(this.cycle)) + ]) .nice(d3.time.month) .range([0, this.width]); this.x = x; @@ -143,20 +150,25 @@ LineChart.prototype.setXScale = function() { LineChart.prototype.setYScale = function() { // Set the y-axis from 0 to the MAX_RANGE ($4 billion) - var y = d3.scale.linear() - .domain([0, Math.ceil(MAX_RANGE / 1000000000) * 1000000000]) - .range([this.height, 0]); + var y = d3.scale + .linear() + .domain([0, Math.ceil(MAX_RANGE / 1000000000) * 1000000000]) + .range([this.height, 0]); return y; }; LineChart.prototype.appendSVG = function() { // Adds a basic SVG container with all the right dimensions - var svg = this.element.append('svg') - .attr('class', 'bar-chart') - .attr('width', '100%') - .attr('height', this.height + this.margin.top + this.margin.bottom) + var svg = this.element + .append('svg') + .attr('class', 'bar-chart') + .attr('width', '100%') + .attr('height', this.height + this.margin.top + this.margin.bottom) .append('g') - .attr('transform', 'translate(' + this.margin.left + ',' + this.margin.top + ')'); + .attr( + 'transform', + 'translate(' + this.margin.left + ',' + this.margin.top + ')' + ); return svg; }; @@ -164,61 +176,74 @@ LineChart.prototype.drawChart = function() { var entityTotals = this.groupEntityTotals(); var x = this.setXScale(); var y = this.setYScale(); - var xAxis = d3.svg.axis() - .scale(x) - .ticks(d3.time.month) - .tickFormat(this.xAxisFormatter()) - .orient('bottom'); - var yAxis = d3.svg.axis() - .scale(y) - .orient('right') - .tickSize(this.width) - .tickFormat(function(d) { - return numeral(d).format('($0.0a)'); - }); + var xAxis = d3.svg + .axis() + .scale(x) + .ticks(d3.time.month) + .tickFormat(this.xAxisFormatter()) + .orient('bottom'); + var yAxis = d3.svg + .axis() + .scale(y) + .orient('right') + .tickSize(this.width) + .tickFormat(function(d) { + return numeral(d).format('($0.0a)'); + }); // Create the base SVG var svg = this.appendSVG(); // Add the xAxis - svg.append('g') - .attr('class', 'x axis') - .attr('transform', 'translate(0,' + this.height + ')') - .call(xAxis); + svg + .append('g') + .attr('class', 'x axis') + .attr('transform', 'translate(0,' + this.height + ')') + .call(xAxis); // Add the yAxis - svg.append('g') - .attr('class', 'y axis') - .call(yAxis) - .selectAll('text') - .attr('y', -4) - .attr('x', -4) - .attr('dy', '.71em') - .style('text-anchor', 'end'); - - var lineBuilder = d3.svg.line() - .x(function(d) { return x(d.date); }) - .y(function(d) { return y(d.amount); }); + svg + .append('g') + .attr('class', 'y axis') + .call(yAxis) + .selectAll('text') + .attr('y', -4) + .attr('x', -4) + .attr('dy', '.71em') + .style('text-anchor', 'end'); + + var lineBuilder = d3.svg + .line() + .x(function(d) { + return x(d.date); + }) + .y(function(d) { + return y(d.amount); + }); // Draw a line and populate data for each entity type this.entityNames.forEach(function(entity) { var line = svg.append('g').attr('class', 'line--' + entity); var points = line.append('g').attr('class', 'line__points'); - line.append('path') + line + .append('path') .datum(entityTotals[entity]) .attr('d', lineBuilder) .attr('stroke-width', 2) .attr('fill', 'none'); - points.selectAll('circle') + points + .selectAll('circle') .data(entityTotals[entity]) .enter() .append('circle') .attr('cx', function(d) { - return x(d.date); + return x(d.date); + }) + .attr('cy', function(d) { + return y(d.amount); }) - .attr('cy', function(d) { return y(d.amount); }) .attr('r', 2); }); @@ -227,11 +252,14 @@ LineChart.prototype.drawChart = function() { LineChart.prototype.drawCursor = function(svg) { // Add a dotted vertical line for the cursor - this.cursor = svg.append('line') + this.cursor = svg + .append('line') .attr('class', 'cursor') .attr('stroke-dasharray', '5,5') - .attr('x1', 10).attr('x2', 10) - .attr('y1', 0).attr('y2', this.height); + .attr('x1', 10) + .attr('x2', 10) + .attr('y1', 0) + .attr('y2', this.height); }; LineChart.prototype.xAxisFormatter = function() { @@ -274,10 +302,11 @@ LineChart.prototype.moveCursor = function(datum) { var target = datum ? datum : this.getCursorStartPosition(); var i = this.chartData.indexOf(target); this.cursor.attr('x1', this.x(target.date)).attr('x2', this.x(target.date)); - this.nextDatum = this.chartData[i+1] || false; - this.prevDatum = this.chartData[i-1] || false; + this.nextDatum = this.chartData[i + 1] || false; + this.prevDatum = this.chartData[i - 1] || false; this.populateSnapshot(target); - this.element.selectAll('.line__points circle') + this.element + .selectAll('.line__points circle') .attr('r', 2) .filter(function(d) { return d.date === target.date; @@ -308,7 +337,11 @@ LineChart.prototype.populateSnapshot = function(datum) { this.snapshotSubtotals(datum); this.snapshotTotal(datum); this.$snapshot.find('.js-max-date').html(parseMDY(datum.date)); - helpers.zeroPad(this.$snapshot, '.snapshot__item-number', '.figure__decimals'); + helpers.zeroPad( + this.$snapshot, + '.snapshot__item-number', + '.figure__decimals' + ); }; LineChart.prototype.snapshotSubtotals = function(datum) { @@ -325,7 +358,9 @@ LineChart.prototype.snapshotTotal = function(datum) { var total = _.chain(datum) .omit('date') .values() - .reduce(function(a, b) { return a + b; }) + .reduce(function(a, b) { + return a + b; + }) .value(); this.$snapshot.find('[data-total-for="all"]').html(helpers.currency(total)); }; diff --git a/fec/fec/static/js/modules/listeners.js b/fec/fec/static/js/modules/listeners.js index 64963a9d3f..ce5455d249 100644 --- a/fec/fec/static/js/modules/listeners.js +++ b/fec/fec/static/js/modules/listeners.js @@ -11,7 +11,7 @@ Listeners.prototype.on = function(elm) { var $elm = $(elm); var args = _.toArray(arguments).slice(1); this.listeners = this._listeners || []; - this.listeners.push({$elm: $elm, args: args}); + this.listeners.push({ $elm: $elm, args: args }); $elm.on.apply($elm, args); }; @@ -23,4 +23,4 @@ Listeners.prototype.clear = function() { }); }; -module.exports = {Listeners: Listeners}; +module.exports = { Listeners: Listeners }; diff --git a/fec/fec/static/js/modules/maps-event.js b/fec/fec/static/js/modules/maps-event.js index 86c2a6d850..1b3f9af1b9 100644 --- a/fec/fec/static/js/modules/maps-event.js +++ b/fec/fec/static/js/modules/maps-event.js @@ -14,9 +14,13 @@ function highlightRowAndState($map, $table, state, scroll) { $scrollBody.find('.row-active').removeClass('row-active'); $row.parents('tr').addClass('row-active'); if (scroll) { - $scrollBody.animate({ - scrollTop: $row.closest('tr').height() * parseInt($row.attr('data-row')) - }, 500); + $scrollBody.animate( + { + scrollTop: + $row.closest('tr').height() * parseInt($row.attr('data-row')) + }, + 500 + ); } } } @@ -24,12 +28,14 @@ function highlightRowAndState($map, $table, state, scroll) { function init($map, $table) { $map.on('click', 'path[data-state]', function() { var state = $(this).attr('data-state'); - events.emit('state.map', {state: state}); + events.emit('state.map', { state: state }); }); $table.on('click', 'tr', function() { events.emit('state.table', { - state: $(this).find('span[data-state]').attr('data-state') + state: $(this) + .find('span[data-state]') + .attr('data-state') }); }); diff --git a/fec/fec/static/js/modules/maps.js b/fec/fec/static/js/modules/maps.js index 3689bd8f1e..02ce5ec474 100644 --- a/fec/fec/static/js/modules/maps.js +++ b/fec/fec/static/js/modules/maps.js @@ -27,12 +27,7 @@ var stateFeatureMap = _.chain(stateFeatures) var colorZero = '#ffffff'; var colorScale = ['#e2ffff', '#278887']; -var compactRules = [ - ['B', 9], - ['M', 6], - ['k', 3], - ['', 0] -]; +var compactRules = [['B', 9], ['M', 6], ['k', 3], ['', 0]]; var MAX_MAPS = 2; _.templateSettings = { @@ -51,11 +46,13 @@ function compactNumber(value, rule) { } function stateMap($elm, data, width, height, min, max, addLegend, addTooltips) { - var svg = d3.select($elm[0]) + var svg = d3 + .select($elm[0]) .append('svg') - .attr('width', width) - .attr('height', height); - var projection = d3.geo.albersUsa() + .attr('width', width) + .attr('height', height); + var projection = d3.geo + .albersUsa() .scale(450) .translate([220, 150]); var path = d3.geo.path().projection(projection); @@ -81,18 +78,20 @@ function stateMap($elm, data, width, height, min, max, addLegend, addTooltips) { max = max || _.max(totals); var scale = chroma.scale(colorScale).domain([min, max]); var quantize = d3.scale.linear().domain([min, max]); - svg.append('g') + svg + .append('g') .selectAll('path') - .data(stateFeatures) - .enter().append('path') - .attr('fill', function(d) { - return results[d.id] ? scale(results[d.id]) : colorZero; - }) - .attr('data-state', function(d) { - return fips.fipsByCode[d.id].STATE_NAME; - }) - .attr('class', 'shape') - .attr('d', path) + .data(stateFeatures) + .enter() + .append('path') + .attr('fill', function(d) { + return results[d.id] ? scale(results[d.id]) : colorZero; + }) + .attr('data-state', function(d) { + return fips.fipsByCode[d.id].STATE_NAME; + }) + .attr('class', 'shape') + .attr('d', path) .on('mouseover', function(d) { if (results[d.id]) { this.parentNode.appendChild(this); @@ -115,12 +114,14 @@ function stateLegend(svg, scale, quantize, quantiles) { var legendWidth = 40; var legendBar = 35; var ticks = quantize.ticks(quantiles); - var legend = svg.selectAll('g.legend') + var legend = svg + .selectAll('g.legend') .data(ticks) .enter() - .append('g') - .attr('class', 'legend'); - legend.append('rect') + .append('g') + .attr('class', 'legend'); + legend + .append('rect') .attr('x', function(d, i) { return i * legendWidth + (legendWidth - legendBar) / 2; }) @@ -133,7 +134,8 @@ function stateLegend(svg, scale, quantize, quantiles) { // Add legend text var compactRule = chooseRule(ticks[Math.ceil(ticks.length / 2)]); - legend.append('text') + legend + .append('text') .attr('x', function(d, i) { return (i + 0.5) * legendWidth; }) @@ -149,26 +151,27 @@ function stateLegend(svg, scale, quantize, quantiles) { var tooltipTemplate = _.template( '<div class="tooltip__title">{{ name }}</div>' + - '<div class="tooltip__value">{{ total }}</div>' + '<div class="tooltip__value">{{ total }}</div>' ); function stateTooltips(svg, path, results) { - var tooltip = d3.select('body').append('div') + var tooltip = d3 + .select('body') + .append('div') .attr('id', 'map-tooltip') .attr('class', 'tooltip tooltip--above tooltip--mouse') .style('position', 'absolute') .style('pointer-events', 'none') .style('display', 'none'); - svg.selectAll('path') + svg + .selectAll('path') .on('mouseover', function(d) { this.parentNode.appendChild(this); var html = tooltipTemplate({ name: fips.fipsByCode[d.id].STATE_NAME, total: helpers.currency(results[d.id] || 0) }); - tooltip - .style('display', 'block') - .html(html); + tooltip.style('display', 'block').html(html); moveTooltip(tooltip); }) .on('mouseout', function() { @@ -180,11 +183,9 @@ function stateTooltips(svg, path, results) { } function moveTooltip(tooltip) { - var x = d3.event.pageX - (tooltip[0][0].offsetWidth / 2); - var y = d3.event.pageY - (tooltip[0][0].offsetHeight); - tooltip - .style('left', x + 'px') - .style('top', y + 'px'); + var x = d3.event.pageX - tooltip[0][0].offsetWidth / 2; + var y = d3.event.pageY - tooltip[0][0].offsetHeight; + tooltip.style('left', x + 'px').style('top', y + 'px'); } function highlightState($parent, state) { @@ -223,7 +224,7 @@ DistrictMap.prototype.render = function(data) { this.elm.setAttribute('aria-hidden', 'false'); this.map = L.map(this.elm); L.tileLayer.provider('Stamen.TonerLite').addTo(this.map); - this.overlay = L.geoJson(data, {style: this.style}).addTo(this.map); + this.overlay = L.geoJson(data, { style: this.style }).addTo(this.map); this.map.fitBounds(this.overlay.getBounds()); }; @@ -253,9 +254,12 @@ function mapMax(cached) { function updateColorScale($container, cached) { $container = $container.closest('#state-maps'); - var displayed = $container.find('.state-map select').map(function(_, select) { - return $(select).val(); - }).get(); + var displayed = $container + .find('.state-map select') + .map(function(_, select) { + return $(select).val(); + }) + .get(); _.each(_.keys(cached), function(key) { if (displayed.indexOf(key) === -1) { delete cached[key]; @@ -291,12 +295,16 @@ function updateButtonsDisplay($parent) { function appendStateMap($parent, results, cached) { var ids = _.pluck(results, 'candidate_id'); - var displayed = $parent.find('.candidate-select').map(function(_, select) { - return $(select).val(); - }).get(); - var value = _.find(ids, function(each) { - return displayed.indexOf(each) === -1; - }) || _.last(ids); + var displayed = $parent + .find('.candidate-select') + .map(function(_, select) { + return $(select).val(); + }) + .get(); + var value = + _.find(ids, function(each) { + return displayed.indexOf(each) === -1; + }) || _.last(ids); $parent.append(candidateStateMapTemplate(results)); var $select = $parent.find('.state-map:last select'); $select.val(value); @@ -308,7 +316,7 @@ function appendStateMap($parent, results, cached) { function drawStateMap($container, candidateId, cached) { var url = helpers.buildUrl( ['schedules', 'schedule_a', 'by_state', 'by_candidate'], - {cycle: context.election.cycle, candidate_id: candidateId, per_page: 99} + { cycle: context.election.cycle, candidate_id: candidateId, per_page: 99 } ); var $map = $container.find('.state-map-choropleth'); $map.html(''); diff --git a/fec/fec/static/js/modules/other-spending-totals.js b/fec/fec/static/js/modules/other-spending-totals.js index 2ea1da2894..70d67eed5d 100644 --- a/fec/fec/static/js/modules/other-spending-totals.js +++ b/fec/fec/static/js/modules/other-spending-totals.js @@ -8,13 +8,13 @@ var _ = require('underscore'); var helpers = require('../modules/helpers'); var pathMap = { - 'independentExpenditures': '/schedules/schedule_e/by_candidate/', - 'communicationCosts': '/communication_costs/by_candidate/', - 'electioneering': '/electioneering/by_candidate/' + independentExpenditures: '/schedules/schedule_e/by_candidate/', + communicationCosts: '/communication_costs/by_candidate/', + electioneering: '/electioneering/by_candidate/' }; function OtherSpendingTotals(type) { - this.$elm = $('.js-other-spending-totals[data-spending-type='+ type + ']'); + this.$elm = $('.js-other-spending-totals[data-spending-type=' + type + ']'); this.type = type; this.data = []; this.init(); @@ -25,16 +25,13 @@ OtherSpendingTotals.prototype.fetchData = function(page) { // Page is required because if there's more than 100 results we need // to loop through all the pages var self = this; - var url = helpers.buildUrl( - pathMap[this.type], - { - candidate_id: context.candidateID, - cycle: context.cycle, - election_full: context.electionFull, - page: page, - per_page: 100 - } - ); + var url = helpers.buildUrl(pathMap[this.type], { + candidate_id: context.candidateID, + cycle: context.cycle, + election_full: context.electionFull, + page: page, + per_page: 100 + }); $.getJSON(url).done(function(data) { var currentPage = data.pagination.page; @@ -64,10 +61,14 @@ OtherSpendingTotals.prototype.showTotals = function(results) { if (this.type === 'electioneering') { // Electioneering comms aren't marked as support or oppose, so just add // them all together - var total = _.reduce(results, function(memo, datum) { - return memo + datum.total; - }, 0); - this.$elm.find('.js-total-electioneering').html(helpers.currency(total)); + var total = _.reduce( + results, + function(memo, datum) { + return memo + datum.total; + }, + 0 + ); + this.$elm.find('.js-total-electioneering').html(helpers.currency(total)); } else { // Get support and oppose totals by filtering results by the correct indicator // and then running _.reduce to add all the values @@ -76,7 +77,7 @@ OtherSpendingTotals.prototype.showTotals = function(results) { return value.support_oppose_indicator === 'S'; }) .reduce(function(memo, datum) { - return memo + datum.total; + return memo + datum.total; }, 0) .value(); @@ -85,14 +86,13 @@ OtherSpendingTotals.prototype.showTotals = function(results) { return value.support_oppose_indicator === 'O'; }) .reduce(function(memo, datum) { - return memo + datum.total; + return memo + datum.total; }, 0) .value(); // Update the DOM with the values this.$elm.find('.js-support').html(helpers.currency(supportTotal)); this.$elm.find('.js-oppose').html(helpers.currency(opposeTotal)); - } }; diff --git a/fec/fec/static/js/modules/performance.js b/fec/fec/static/js/modules/performance.js index b0b15a9ede..ef7889987f 100644 --- a/fec/fec/static/js/modules/performance.js +++ b/fec/fec/static/js/modules/performance.js @@ -7,12 +7,12 @@ var _ = require('underscore'); require('perfbar/build/perfbar'); var performanceBudgets = { - loadTime: {max: 3000}, - latency: {max: 10000}, - FirstPaint: {max: 150}, - frontEnd: {max: 1200}, - backEnd: {max: 300}, - large_search_loaded: {max: 400} + loadTime: { max: 3000 }, + latency: { max: 10000 }, + FirstPaint: { max: 150 }, + frontEnd: { max: 1200 }, + backEnd: { max: 300 }, + large_search_loaded: { max: 400 } }; function getMetric(mark) { @@ -26,12 +26,13 @@ function getMetric(mark) { } function bar() { - var marks = window.performance && - window.performance.getEntriesByType && - window.performance.getEntriesByType('mark') || + var marks = + (window.performance && + window.performance.getEntriesByType && + window.performance.getEntriesByType('mark')) || []; - perfBar.init({budget: performanceBudgets}); + perfBar.init({ budget: performanceBudgets }); _.chain(marks) .groupBy('name') @@ -41,4 +42,4 @@ function bar() { }); } -module.exports = {bar: bar}; +module.exports = { bar: bar }; diff --git a/fec/fec/static/js/modules/reaction-box.js b/fec/fec/static/js/modules/reaction-box.js index 301bc86ca7..981969e822 100644 --- a/fec/fec/static/js/modules/reaction-box.js +++ b/fec/fec/static/js/modules/reaction-box.js @@ -53,10 +53,10 @@ ReactionBox.prototype.showTextarea = function() { this.$step2.attr('aria-hidden', false); var labelMap = { - 'informative': 'Great! \n What did you learn?', - 'confusing': 'We’re sorry to hear that. What didn\'t make sense?', + informative: 'Great! \n What did you learn?', + confusing: "We’re sorry to hear that. What didn't make sense?", 'not-interested': 'We’re sorry to hear that. What would you like to see?', - 'none': 'How we can make this better?' + none: 'How we can make this better?' }; this.$step2.find('label').text(labelMap[this.reaction]); @@ -100,4 +100,3 @@ ReactionBox.prototype.handleReset = function() { }; module.exports = { ReactionBox: ReactionBox }; - diff --git a/fec/fec/static/js/modules/search.js b/fec/fec/static/js/modules/search.js index 41aa6df073..f89bc0ef91 100644 --- a/fec/fec/static/js/modules/search.js +++ b/fec/fec/static/js/modules/search.js @@ -8,8 +8,8 @@ var KEYCODE_SLASH = 191; var defaultOpts = { placeHolderOptions: { - 'candidates': 'Search candidates', - 'committees': 'Search committees' + candidates: 'Search candidates', + committees: 'Search committees' } }; @@ -19,14 +19,14 @@ function onSelectChange($input, updatedText) { var Search = function($el, opts) { var $select = $el.find('.js-search-type'), - $input = $el.find('input[type="text"]'), - settings = $.extend( {}, defaultOpts, opts); + $input = $el.find('input[type="text"]'), + settings = $.extend({}, defaultOpts, opts); if (settings.placeHolderOptions) { $select.on('change', function(ev) { ev.preventDefault(); var value = $(this).val(); - events.emit('searchTypeChanged', {type: value}); + events.emit('searchTypeChanged', { type: value }); onSelectChange($input, settings.placeHolderOptions[value]); }); } @@ -37,7 +37,6 @@ var Search = function($el, opts) { $input.first().focus(); } }); - }; module.exports = Search; diff --git a/fec/fec/static/js/modules/site-nav.js b/fec/fec/static/js/modules/site-nav.js index ec369ad3c2..13573765eb 100644 --- a/fec/fec/static/js/modules/site-nav.js +++ b/fec/fec/static/js/modules/site-nav.js @@ -36,9 +36,12 @@ SiteNav.prototype.initMenu = function() { SiteNav.prototype.initMegaMenu = function() { this.$element.find('[data-submenu]').each(function() { // Remove hrefs and default click behavior for links that have submenus - $(this).find('.site-nav__link').attr('href', '#0').on('click', function(e) { - e.preventDefault(); - }); + $(this) + .find('.site-nav__link') + .attr('href', '#0') + .on('click', function(e) { + e.preventDefault(); + }); }); this.$menu.accessibleMegaMenu({ @@ -73,7 +76,7 @@ SiteNav.prototype.toggleMenu = function() { SiteNav.prototype.showMenu = function() { this.$body.css({ - 'overflow': 'hidden' + overflow: 'hidden' }); this.$element.addClass('is-open'); this.$toggle.addClass('active'); @@ -83,7 +86,7 @@ SiteNav.prototype.showMenu = function() { SiteNav.prototype.hideMenu = function() { this.$body.css({ - 'overflow': 'auto' + overflow: 'auto' }); this.$element.removeClass('is-open'); this.$toggle.removeClass('active'); diff --git a/fec/fec/static/js/modules/skip-nav.js b/fec/fec/static/js/modules/skip-nav.js index 37835f74e5..22cb6307a2 100644 --- a/fec/fec/static/js/modules/skip-nav.js +++ b/fec/fec/static/js/modules/skip-nav.js @@ -5,32 +5,37 @@ var $ = require('jquery'); /** - * Skip nav link - * @constructor - * @param {string} anchor - CSS selector for the anchor element that will function as the skip nav - * @param {string} targetBody - CSS selector for the main content area to look for a focusable element in - */ + * Skip nav link + * @constructor + * @param {string} anchor - CSS selector for the anchor element that will function as the skip nav + * @param {string} targetBody - CSS selector for the main content area to look for a focusable element in + */ function Skipnav(anchor, targetBody) { this.anchor = anchor; this.$targetBody = $(targetBody); this.$target = $(this.findTarget()); - $(document.body).on('click keyup', this.anchor, this.focusOnTarget.bind(this)); + $(document.body).on( + 'click keyup', + this.anchor, + this.focusOnTarget.bind(this) + ); } Skipnav.prototype.findTarget = function() { - return this.$targetBody.find(':first-child') + return this.$targetBody + .find(':first-child') .not('div, header, section, article, aside') .filter(':visible')[0]; }; Skipnav.prototype.focusOnTarget = function(e) { e.preventDefault(); - + if (e.keyCode === 13 || e.type === 'click') { - this.$target.attr('tabindex','0'); + this.$target.attr('tabindex', '0'); this.$target.focus(); } }; -module.exports = {Skipnav: Skipnav}; +module.exports = { Skipnav: Skipnav }; diff --git a/fec/fec/static/js/modules/table-columns.js b/fec/fec/static/js/modules/table-columns.js index 6cf4498e4a..9f218d4d5c 100644 --- a/fec/fec/static/js/modules/table-columns.js +++ b/fec/fec/static/js/modules/table-columns.js @@ -16,13 +16,13 @@ var candidateInformationColumns = [ data, helpers.buildAppUrl(['candidate', row.candidate_id]), 'candidate', - {isIncumbent: row.incumbent_challenge_full === 'Incumbent'} + { isIncumbent: row.incumbent_challenge_full === 'Incumbent' } ); } }, { data: 'party_full', - className: 'all column--large', + className: 'all column--large' }, { data: 'candidate_pcc_name', @@ -46,21 +46,26 @@ var candidateInformationColumns = [ ]; var communicationCostColumns = [ - columns.committeeColumn({data: 'committee', className: 'all'}), + columns.committeeColumn({ data: 'committee', className: 'all' }), columns.supportOpposeColumn, - columns.candidateColumn({data: 'candidate', className: 'all'}), + columns.candidateColumn({ data: 'candidate', className: 'all' }), { data: 'total', className: 'all column--number', orderable: true, orderSequence: ['desc', 'asc'], - render: columnHelpers.buildTotalLink(['communication-costs'], function(data, type, row, meta) { - return { - support_oppose_indicator: row.support_oppose_indicator, - candidate_id: row.candidate_id, - }; + render: columnHelpers.buildTotalLink(['communication-costs'], function( + data, + type, + row, + meta + ) { + return { + support_oppose_indicator: row.support_oppose_indicator, + candidate_id: row.candidate_id + }; }) - }, + } ]; function createElectionColumns(context) { @@ -73,7 +78,7 @@ function createElectionColumns(context) { data, helpers.buildAppUrl(['candidate', row.candidate_id]), 'candidate', - {isIncumbent: row.incumbent_challenge_full === 'Incumbent'} + { isIncumbent: row.incumbent_challenge_full === 'Incumbent' } ); } }, @@ -102,17 +107,16 @@ function createElectionColumns(context) { if (context.election.office === 'president') { urlBase = ['reports', 'presidential']; } else { - urlBase = ['reports','house-senate']; + urlBase = ['reports', 'house-senate']; } - var url = helpers.buildAppUrl( - urlBase, - { - committee_id: row.committee_ids, - cycle: context.election.cycle, - is_amended: 'false' - } - ); - var coverage_end_date = row.coverage_end_date ? moment(row.coverage_end_date).format('MM/DD/YYYY') : null; + var url = helpers.buildAppUrl(urlBase, { + committee_id: row.committee_ids, + cycle: context.election.cycle, + is_amended: 'false' + }); + var coverage_end_date = row.coverage_end_date + ? moment(row.coverage_end_date).format('MM/DD/YYYY') + : null; return coverageEndDate({ coverage_end_date: coverage_end_date, @@ -120,45 +124,53 @@ function createElectionColumns(context) { }); }, className: 'all', - orderable: false, + orderable: false } ]; } var electioneeringColumns = [ - columns.committeeColumn({data: 'committee', className: 'all'}), - columns.candidateColumn({data: 'candidate', className: 'all'}), + columns.committeeColumn({ data: 'committee', className: 'all' }), + columns.candidateColumn({ data: 'candidate', className: 'all' }), { data: 'total', className: 'all column--number', orderable: true, orderSequence: ['desc', 'asc'], - render: columnHelpers.buildTotalLink(['electioneering-communications'], function(data, type, row, meta) { + render: columnHelpers.buildTotalLink( + ['electioneering-communications'], + function(data, type, row, meta) { return { - candidate_id: row.candidate_id, + candidate_id: row.candidate_id }; - }) - }, + } + ) + } ]; var independentExpenditureColumns = [ - columns.committeeColumn({data: 'committee', className: 'all'}), + columns.committeeColumn({ data: 'committee', className: 'all' }), columns.supportOpposeColumn, - columns.candidateColumn({data: 'candidate', className: 'all'}), + columns.candidateColumn({ data: 'candidate', className: 'all' }), { data: 'total', className: 'all column--number', orderable: true, orderSequence: ['desc', 'asc'], - render: columnHelpers.buildTotalLink(['independent-expenditures'], function(data, type, row, meta) { - return { - data_type: 'processed', - is_notice: 'false', - support_oppose_indicator: row.support_oppose_indicator, - candidate_id: row.candidate_id - }; + render: columnHelpers.buildTotalLink(['independent-expenditures'], function( + data, + type, + row, + meta + ) { + return { + data_type: 'processed', + is_notice: 'false', + support_oppose_indicator: row.support_oppose_indicator, + candidate_id: row.candidate_id + }; }) - }, + } ]; module.exports = { diff --git a/fec/fec/static/js/modules/table-panels.js b/fec/fec/static/js/modules/table-panels.js index a947092452..87d6e23b01 100644 --- a/fec/fec/static/js/modules/table-panels.js +++ b/fec/fec/static/js/modules/table-panels.js @@ -12,15 +12,13 @@ function getCandidateCommittees(row, cycle, electionFull) { // Build the URL and make a call to the history endpoint var url = helpers.buildUrl( ['candidate', row.candidate_id, 'committees', 'history', cycle], - {'election_full': electionFull} + { election_full: electionFull } ); return $.getJSON(url).then(function(response) { - var results = response.results.length ? - response.results : - {}; + var results = response.results.length ? response.results : {}; var principalCommittees = [], - authorizedCommittees = []; + authorizedCommittees = []; results.forEach(function(result) { if (result.designation === 'P') { @@ -38,62 +36,59 @@ function getCandidateCommittees(row, cycle, electionFull) { } function getCandidateFilings(row) { - var url = helpers.buildUrl( - ['candidate', row.candidate_id, 'filings'], - {form_type: 'F2'} - ); + var url = helpers.buildUrl(['candidate', row.candidate_id, 'filings'], { + form_type: 'F2' + }); return $.getJSON(url).then(function(response) { - var results = response.results.length ? - response.results : - {}; + var results = response.results.length ? response.results : {}; - return {last: results[0], first: results[results.length - 1]}; + return { last: results[0], first: results[results.length - 1] }; }); } var renderCandidatePanel = function(showFinancialTotals) { - return tables.modalRenderFactory( - candidatesTemplate, - function(row) { - var query = URI.parseQuery(window.location.search); - // Parse all of the time-related variables - var electionYear = query.election_year; - var cycle = query.cycle || query.election_year || $('#cycle').val(); - var electionFull = query.election_full === 'true' ? true : false; + return tables.modalRenderFactory(candidatesTemplate, function(row) { + var query = URI.parseQuery(window.location.search); + // Parse all of the time-related variables + var electionYear = query.election_year; + var cycle = query.cycle || query.election_year || $('#cycle').val(); + var electionFull = query.election_full === 'true' ? true : false; - // Build a string showing the range covered by the financial totals - // Only relevant on office pages - var timePeriod = showFinancialTotals ? - helpers.getTimePeriod(electionYear, cycle, electionFull, row.office) : - null; + // Build a string showing the range covered by the financial totals + // Only relevant on office pages + var timePeriod = showFinancialTotals + ? helpers.getTimePeriod(electionYear, cycle, electionFull, row.office) + : null; - if (showFinancialTotals) { - return $.when(getCandidateCommittees(row, cycle, electionFull), getCandidateFilings(row)).then(function(data1, data2) { - var newData = { - committees: data1, - first_form_2: data2.first, - last_form_2: data2.last, - query: query, - time_period: timePeriod, - showFinancialTotals: showFinancialTotals - }; - return _.extend({}, row, newData); - }); - } else { - return $.when(getCandidateFilings(row)).then(function(data) { - var newData = { - first_form_2: data.first, - last_form_2: data.last, - showFinancialTotals: showFinancialTotals - }; - return _.extend({}, row, newData); - }); - } + if (showFinancialTotals) { + return $.when( + getCandidateCommittees(row, cycle, electionFull), + getCandidateFilings(row) + ).then(function(data1, data2) { + var newData = { + committees: data1, + first_form_2: data2.first, + last_form_2: data2.last, + query: query, + time_period: timePeriod, + showFinancialTotals: showFinancialTotals + }; + return _.extend({}, row, newData); + }); + } else { + return $.when(getCandidateFilings(row)).then(function(data) { + var newData = { + first_form_2: data.first, + last_form_2: data.last, + showFinancialTotals: showFinancialTotals + }; + return _.extend({}, row, newData); + }); } - ); + }); }; module.exports = { - renderCandidatePanel: renderCandidatePanel, + renderCandidatePanel: renderCandidatePanel }; diff --git a/fec/fec/static/js/modules/table-switcher.js b/fec/fec/static/js/modules/table-switcher.js index 77d19961f7..154665ee56 100644 --- a/fec/fec/static/js/modules/table-switcher.js +++ b/fec/fec/static/js/modules/table-switcher.js @@ -29,8 +29,10 @@ TableSwitcher.prototype.handleChange = function(e) { TableSwitcher.prototype.toggleMessage = function(table) { // Hide the visible message and show the message for the selected toggle - this.$control.find('.js-table-switcher-message[aria-hidden="false"]').attr('aria-hidden', true); + this.$control + .find('.js-table-switcher-message[aria-hidden="false"]') + .attr('aria-hidden', true); this.$control.find('#' + table + '-message').attr('aria-hidden', false); }; -module.exports = {TableSwitcher: TableSwitcher}; +module.exports = { TableSwitcher: TableSwitcher }; diff --git a/fec/fec/static/js/modules/tables.js b/fec/fec/static/js/modules/tables.js index 982a9ca0f6..62c028db36 100644 --- a/fec/fec/static/js/modules/tables.js +++ b/fec/fec/static/js/modules/tables.js @@ -25,24 +25,26 @@ var exportWidgetTemplate = require('../templates/tables/exportWidget.hbs'); var missingTemplate = require('../templates/tables/noData.hbs'); var simpleDOM = 't<"results-info"lpi>'; -var browseDOM = '<"panel__main"t>' + - '<"results-info"lp>'; +var browseDOM = '<"panel__main"t>' + '<"results-info"lp>'; var DOWNLOAD_CAP = 500000; var downloadCapFormatted = helpers.formatNumber(DOWNLOAD_CAP); var MAX_DOWNLOADS = 5; var DOWNLOAD_MESSAGES = { recordCap: - 'Use <a href="' + window.BASE_PATH + '/advanced?tab=bulk-data">' + + 'Use <a href="' + + window.BASE_PATH + + '/advanced?tab=bulk-data">' + 'bulk data</a> to export more than ' + downloadCapFormatted + ' records.', - downloadCap: 'Each user is limited to ' + + downloadCap: + 'Each user is limited to ' + MAX_DOWNLOADS + ' exports at a time. This helps us keep things running smoothly.', empty: 'This table has no data to export.', comingSoon: 'Data exports for this page are coming soon.', - pending: 'You\'re already exporting this data set.' + pending: "You're already exporting this data set." }; var DATA_WIDGETS = '.js-data-widgets'; @@ -54,7 +56,7 @@ var messageTimer; // Only show table after draw $(document.body).on('draw.dt', function() { $('.dataTable tbody').attr('role', 'rowgroup'); - $('.dataTable tbody td:first-child').attr('scope','row'); + $('.dataTable tbody td:first-child').attr('scope', 'row'); }); function yearRange(first, last) { @@ -71,12 +73,12 @@ function getCycle(value, meta) { if (filters && filters.cycle) { var cycles = _.intersection( - _.map(filters.cycle, function(cycle) { return parseInt(cycle); }), + _.map(filters.cycle, function(cycle) { + return parseInt(cycle); + }), value ); - return cycles.length ? - {cycle: _.max(cycles)} : - {}; + return cycles.length ? { cycle: _.max(cycles) } : {}; } else { return {}; } @@ -94,7 +96,9 @@ function mapSort(order, column) { function getCount(response) { // for audit data set, retrun real data result rows - if (window.location.pathname === '/legal-resources/enforcement/audit-search/'){ + if ( + window.location.pathname === '/legal-resources/enforcement/audit-search/' + ) { return response.pagination.count; } var pagination_count = response.pagination.count; @@ -121,9 +125,10 @@ function identity(value) { } var MODAL_TRIGGER_CLASS = 'js-panel-trigger'; -var MODAL_TRIGGER_HTML = '<button class="js-panel-button button--panel">' + +var MODAL_TRIGGER_HTML = + '<button class="js-panel-button button--panel">' + '<span class="u-visually-hidden">Toggle details</span>' + -'</button>'; + '</button>'; function modalRenderRow(row, data, index) { row.classList.add(MODAL_TRIGGER_CLASS, 'row--has-panel'); @@ -144,7 +149,11 @@ function modalRenderFactory(template, fetch) { // Add a class to the .dataTables_wrapper $table.closest('.dataTables_wrapper').addClass('dataTables_wrapper--panel'); - $table.off('click keypress', '.js-panel-toggle tr.' + MODAL_TRIGGER_CLASS, callback); + $table.off( + 'click keypress', + '.js-panel-toggle tr.' + MODAL_TRIGGER_CLASS, + callback + ); callback = function(e) { if (e.which === 13 || e.type === 'click') { // Note: Use `currentTarget` to get parent row, since the target column @@ -185,7 +194,11 @@ function modalRenderFactory(template, fetch) { } } }; - $table.on('click keypress', '.js-panel-toggle tr.' + MODAL_TRIGGER_CLASS, callback); + $table.on( + 'click keypress', + '.js-panel-toggle tr.' + MODAL_TRIGGER_CLASS, + callback + ); $modal.on('click', '.js-panel-close', function(e) { e.preventDefault(); @@ -195,21 +208,21 @@ function modalRenderFactory(template, fetch) { } function hidePanel(api, $modal) { - $('.row-active .js-panel-button').focus(); - $('.js-panel-toggle tr').toggleClass('row-active', false); - $('body').toggleClass('panel-active', false); - $modal.attr('aria-hidden', 'true'); - - if ($(document).width() > 640) { - api.columns('.hide-panel-tablet').visible(true); - api.columns('.hide-panel.min-tablet').visible(true); - } + $('.row-active .js-panel-button').focus(); + $('.js-panel-toggle tr').toggleClass('row-active', false); + $('body').toggleClass('panel-active', false); + $modal.attr('aria-hidden', 'true'); - if ($(document).width() > 980) { - api.columns('.hide-panel').visible(true); - } + if ($(document).width() > 640) { + api.columns('.hide-panel-tablet').visible(true); + api.columns('.hide-panel.min-tablet').visible(true); + } - accessibility.removeTabindex($modal); + if ($(document).width() > 980) { + api.columns('.hide-panel').visible(true); + } + + accessibility.removeTabindex($modal); } function barsAfterRender(template, api, data, response) { @@ -230,13 +243,17 @@ function barsAfterRender(template, api, data, response) { var tableMax = $table.data('max'); $cols.after(function() { var value = $(this).attr('data-value'); - var width = 100 * parseFloat(value) / tableMax; + var width = (100 * parseFloat(value)) / tableMax; if ($(this).next('.bar-container').length > 0) { return; } else { - return '<div class="bar-container">' + - '<div class="value-bar" style="width: ' + width + '%"></div>' + - '</div>'; + return ( + '<div class="bar-container">' + + '<div class="value-bar" style="width: ' + + width + + '%"></div>' + + '</div>' + ); } }); } @@ -281,25 +298,24 @@ function filterSuccessUpdates(changeCount) { } else if (type === 'radio') { // Add the message after the last radio button / toggle $label = $('label[for="' + updateChangedEl.id + '"]').closest('fieldset'); - filterAction = $elm.attr('name') == 'data_type' ? - 'Data type changed.' : 'Filter applied.'; + filterAction = + $elm.attr('name') == 'data_type' + ? 'Data type changed.' + : 'Filter applied.'; if (!$elm.is(':checked')) { filterAction = 'Filter removed.'; } - } - else if (type === 'text') { + } else if (type === 'text') { // typeahead if ($elm.hasClass('tt-input')) { // show message after generated checkbox (last item in list) $label = $('[data-filter="typeahead"] li').last(); filterAction = 'Filter added'; - } - else if ($elm.closest('.range').hasClass('range--currency')) { + } else if ($elm.closest('.range').hasClass('range--currency')) { $label = $elm.closest('.range'); filterAction = 'Filter applied'; - } - else if ($elm.closest('.range').hasClass('range--date')) { + } else if ($elm.closest('.range').hasClass('range--date')) { $label = $('.date-range-grid'); filterAction = 'Filter applied'; } @@ -308,24 +324,23 @@ function filterSuccessUpdates(changeCount) { $label = $('.is-loading:not(.overlay)'); filterAction = 'Filter added'; } - } - else { + } else { // probably a select dropdown $label = $elm; filterAction = '"' + $elm.find('option:selected').text() + '" applied.'; } - $('.is-loading:not(.overlay)').removeClass('is-loading').addClass('is-successful'); + $('.is-loading:not(.overlay)') + .removeClass('is-loading') + .addClass('is-successful'); // build message with number of results returned if (changeCount > 0) { filterResult = changeCount.toLocaleString() + ' more results'; - } - else if (changeCount === 0) { + } else if (changeCount === 0) { filterResult = 'No change in results'; - } - else { + } else { filterResult = Math.abs(changeCount).toLocaleString() + ' fewer results'; } @@ -342,19 +357,25 @@ function filterSuccessUpdates(changeCount) { // so we check to make sure the message isn't shown inside the dropdown panel. if ($label.closest('.dropdown__list').length < 1) { // append filter change count message after input - $label.after($('<div class="filter__message filter__message--success">' + message + '</div>') - .hide().fadeIn()); + $label.after( + $( + '<div class="filter__message filter__message--success">' + + message + + '</div>' + ) + .hide() + .fadeIn() + ); } messageTimer = setTimeout(function() { $('.is-successful').removeClass('is-successful'); - $('.filter__message').fadeOut(function () { + $('.filter__message').fadeOut(function() { $(this).remove(); }); $('.date-range-grid').fadeOut(); - }, helpers.SUCCESS_DELAY); } } @@ -364,7 +385,7 @@ function OffsetPaginator() {} OffsetPaginator.prototype.mapQuery = function(data) { return { per_page: data.length, - page: Math.floor(data.start / data.length) + 1, + page: Math.floor(data.start / data.length) + 1 }; }; @@ -395,30 +416,38 @@ SeekPaginator.prototype.mapQuery = function(data, query) { } var indexes = this.getIndexes(data.length, data.start); return _.extend( - {per_page: data.length}, + { per_page: data.length }, _.chain(Object.keys(indexes)) - .filter(function(key) { return indexes[key]; }) - .map(function(key) { return [key, indexes[key]]; }) + .filter(function(key) { + return indexes[key]; + }) + .map(function(key) { + return [key, indexes[key]]; + }) .object() .value() ); }; SeekPaginator.prototype.handleResponse = function(data, response) { - this.setIndexes(data.length, data.length + data.start, response.pagination.last_indexes); + this.setIndexes( + data.length, + data.length + data.start, + response.pagination.last_indexes + ); }; var defaultOpts = { serverSide: true, searching: false, lengthMenu: [30, 50, 100], - responsive: {details: false}, + responsive: { details: false }, language: { - lengthMenu: 'Results per page: _MENU_', + lengthMenu: 'Results per page: _MENU_' }, pagingType: 'simple', title: null, - dom: browseDOM, + dom: browseDOM }; var defaultCallbacks = { @@ -428,7 +457,7 @@ var defaultCallbacks = { function DataTable(selector, opts) { opts = opts || {}; this.$body = $(selector); - this.opts = _.extend({}, defaultOpts, {ajax: this.fetch.bind(this)}, opts); + this.opts = _.extend({}, defaultOpts, { ajax: this.fetch.bind(this) }, opts); this.callbacks = _.extend({}, defaultCallbacks, opts.callbacks); this.xhr = null; this.fetchContext = null; @@ -477,8 +506,7 @@ DataTable.prototype.initFilters = function() { resultType: 'results', showResultCount: true }); - this.$widgets.find('.js-filter-tags').prepend(tagList. - $body); + this.$widgets.find('.js-filter-tags').prepend(tagList.$body); this.filterPanel = new FilterPanel(); this.filterSet = this.filterPanel.filterSet; $(window).on('popstate', this.handlePopState.bind(this)); @@ -489,18 +517,18 @@ DataTable.prototype.refreshExport = function() { if (this.opts.useExport && !this.opts.disableExport) { var numRows = this.api.context[0].fnRecordsTotal(); if (numRows > DOWNLOAD_CAP) { - this.disableExport({message: DOWNLOAD_MESSAGES.recordCap}); + this.disableExport({ message: DOWNLOAD_MESSAGES.recordCap }); } else if (numRows === 0) { - this.disableExport({message: DOWNLOAD_MESSAGES.empty}); + this.disableExport({ message: DOWNLOAD_MESSAGES.empty }); } else if (this.isPending()) { - this.disableExport({message: DOWNLOAD_MESSAGES.pending}); + this.disableExport({ message: DOWNLOAD_MESSAGES.pending }); } else if (download.pendingCount() >= MAX_DOWNLOADS) { - this.disableExport({message: DOWNLOAD_MESSAGES.downloadCap}); + this.disableExport({ message: DOWNLOAD_MESSAGES.downloadCap }); } else { this.enableExport(); } } else if (this.opts.disableExport) { - this.disableExport({message: DOWNLOAD_MESSAGES.comingSoon}); + this.disableExport({ message: DOWNLOAD_MESSAGES.comingSoon }); } else if (this.opts.aggregateExport) { // If it's an aggregate table, just enable the export this.enableExport(); @@ -521,12 +549,14 @@ DataTable.prototype.handlePopState = function() { }; DataTable.prototype.ensureWidgets = function() { - if (this.hasWidgets) { return; } + if (this.hasWidgets) { + return; + } this.$processing = $('<div class="overlay is-loading"></div>').hide(); this.$body.before(this.$processing); if (this.opts.useExport) { - this.$exportWidget = $(exportWidgetTemplate({title: this.opts.title})); + this.$exportWidget = $(exportWidgetTemplate({ title: this.opts.title })); this.$widgets.prepend(this.$exportWidget); this.$exportButton = $('.js-export'); this.$exportMessage = $('.js-export-message'); @@ -537,15 +567,19 @@ DataTable.prototype.ensureWidgets = function() { } if (this.opts.disableExport) { - this.disableExport({message: DOWNLOAD_MESSAGES.comingSoon}); + this.disableExport({ message: DOWNLOAD_MESSAGES.comingSoon }); } if (this.opts.aggregateExport || this.opts.singleEntityItemizedExport) { - this.$exportButton = $('[data-export-for="' + this.$body.data('type') + '"]'); + this.$exportButton = $( + '[data-export-for="' + this.$body.data('type') + '"]' + ); } if (this.opts.singleEntityItemizedExport) { - this.$exportMessage = $('[data-export-message-for="' + this.$body.data('type') + '"]'); + this.$exportMessage = $( + '[data-export-message-for="' + this.$body.data('type') + '"]' + ); } this.hasWidgets = true; @@ -600,7 +634,7 @@ DataTable.prototype.fetch = function(data, callback) { DataTable.prototype.export = function() { var url = this.buildUrl(this.api.ajax.params(), false); download.download(url, false, true); - this.disableExport({message: DOWNLOAD_MESSAGES.pending}); + this.disableExport({ message: DOWNLOAD_MESSAGES.pending }); }; DataTable.prototype.isPending = function() { @@ -609,7 +643,7 @@ DataTable.prototype.isPending = function() { }; DataTable.prototype.buildUrl = function(data, paginate) { - var query = _.extend({sort_hide_null: true}, this.filters || {}); + var query = _.extend({ sort_hide_null: true }, this.filters || {}); paginate = typeof paginate === 'undefined' ? true : paginate; query.sort = mapSort(data.order, this.opts.columns); @@ -617,7 +651,10 @@ DataTable.prototype.buildUrl = function(data, paginate) { query = _.extend(query, this.paginator.mapQuery(data, query)); } - return helpers.buildUrl(this.opts.path, _.extend({}, query, this.opts.query || {})); + return helpers.buildUrl( + this.opts.path, + _.extend({}, query, this.opts.query || {}) + ); }; DataTable.prototype.fetchSuccess = function(resp) { @@ -630,9 +667,12 @@ DataTable.prototype.fetchSuccess = function(resp) { var changeCount = this.newCount - this.currentCount; - var countHTML = this.newCount > 0 ? - 'about <span class="tags__count">' + this.newCount.toLocaleString('en-US') + '</span>' : - '<span class="tags__count">0</span>'; + var countHTML = + this.newCount > 0 + ? 'about <span class="tags__count">' + + this.newCount.toLocaleString('en-US') + + '</span>' + : '<span class="tags__count">0</span>'; this.$widgets.find('.js-count').html(countHTML); filterSuccessUpdates(changeCount); @@ -651,25 +691,36 @@ DataTable.prototype.fetchSuccess = function(resp) { DataTable.prototype.fetchError = function() { var self = this; - var errorMessage = '<div class="filter__message filter__message--error">' + - '<strong>We had trouble processing your request</strong><br>' + - 'Please try again. If you still have trouble, ' + - '<button class="js-filter-feedback">let us know</button></div>'; + var errorMessage = + '<div class="filter__message filter__message--error">' + + '<strong>We had trouble processing your request</strong><br>' + + 'Please try again. If you still have trouble, ' + + '<button class="js-filter-feedback">let us know</button></div>'; $('.filter__message').remove(); - if ($(updateChangedEl).parent().hasClass('range__input')) { - $(updateChangedEl).closest('.range').after($(errorMessage)); - } - else if ($(updateChangedEl).attr('type') === 'text' && $(updateChangedEl).hasClass('tt-input') === false) { - $(updateChangedEl).parent().after($(errorMessage)); - } - else { + if ( + $(updateChangedEl) + .parent() + .hasClass('range__input') + ) { + $(updateChangedEl) + .closest('.range') + .after($(errorMessage)); + } else if ( + $(updateChangedEl).attr('type') === 'text' && + $(updateChangedEl).hasClass('tt-input') === false + ) { + $(updateChangedEl) + .parent() + .after($(errorMessage)); + } else { $('label.is-loading') .removeClass('is-loading') .addClass('is-unsuccessful') .after($(errorMessage)) - .hide().fadeIn(); + .hide() + .fadeIn(); } $('button.is-loading').removeClass('is-loading'); @@ -739,7 +790,7 @@ function initSpendingTables(className, context, options) { hideEmptyOpts: { dataType: opts.title, name: 'this election', - timePeriod: context.timePeriod, + timePeriod: context.timePeriod } }); } @@ -748,13 +799,15 @@ function initSpendingTables(className, context, options) { function refreshTables(e, context) { var $comparison = $('#comparison'); - var selected = $comparison.find('input[type="checkbox"]:checked').map(function(_, input) { - var $input = $(input); - return { - candidate_id: $input.attr('data-id'), - candidate_name: $input.attr('data-name') - }; - }); + var selected = $comparison + .find('input[type="checkbox"]:checked') + .map(function(_, input) { + var $input = $(input); + return { + candidate_id: $input.attr('data-id'), + candidate_name: $input.attr('data-name') + }; + }); if (selected.length > 0) { drawSizeTable(selected, context); @@ -762,10 +815,15 @@ function refreshTables(e, context) { } if (e) { - $(e.target).next('label').addClass('is-loading'); + $(e.target) + .next('label') + .addClass('is-loading'); setTimeout(function() { - $comparison.find('.is-loading').removeClass('is-loading').addClass('is-successful'); + $comparison + .find('.is-loading') + .removeClass('is-loading') + .addClass('is-successful'); }, helpers.LOADING_DELAY); setTimeout(function() { @@ -776,7 +834,7 @@ function refreshTables(e, context) { function drawComparison(results, pageContext) { var $comparison = $('#comparison'); - var context = {selected: results.slice(0, 10), options: results.slice(10)}; + var context = { selected: results.slice(0, 10), options: results.slice(10) }; $comparison.prepend(comparisonTemplate(context)); new dropdown.Dropdown($comparison.find('.js-dropdown')); $comparison.on('change', 'input[type="checkbox"]', function(e) { @@ -792,11 +850,10 @@ function mapSize(response, primary) { groups[result.candidate_id][result.size] = result.total; }); return _.map(_.pairs(groups), function(pair) { - return _.extend( - pair[1], { - candidate_id: pair[0], - candidate_name: primary[pair[0]].candidate_name - }); + return _.extend(pair[1], { + candidate_id: pair[0], + candidate_name: primary[pair[0]].candidate_name + }); }); } @@ -808,8 +865,7 @@ function mapState(response) { groups[result.state].state_full = result.state_full; }); return _.map(_.pairs(groups), function(pair) { - return _.extend( - pair[1], {state: pair[0]}); + return _.extend(pair[1], { state: pair[0] }); }); } @@ -832,7 +888,6 @@ function buildUrl(selected, context, path) { return helpers.buildUrl(path, query); } - var drawTableOpts = { autoWidth: false, destroy: true, @@ -843,27 +898,39 @@ var drawTableOpts = { singleEntityItemizedExport: true, dom: simpleDOM, language: { - lengthMenu: 'Results per page: _MENU_', + lengthMenu: 'Results per page: _MENU_' }, pagingType: 'simple' -} +}; function drawSizeTable(selected, context) { var $table = $('table[data-type="by-size"]'); - var primary = _.object(_.map(selected, function(result) { - return [result.candidate_id, result]; - })); + var primary = _.object( + _.map(selected, function(result) { + return [result.candidate_id, result]; + }) + ); $.getJSON( - buildUrl(selected, context, ['schedules', 'schedule_a', 'by_size', 'by_candidate']) + buildUrl(selected, context, [ + 'schedules', + 'schedule_a', + 'by_size', + 'by_candidate' + ]) ).done(function(response) { var data = mapSize(response, primary); destroyTable($table); - $table.dataTable(_.extend({ - autoWidth: false, - data: data, - columns: columnHelpers.sizeColumns(context), - order: [[1, 'desc']] - }, drawTableOpts)); + $table.dataTable( + _.extend( + { + autoWidth: false, + data: data, + columns: columnHelpers.sizeColumns(context), + order: [[1, 'desc']] + }, + drawTableOpts + ) + ); barsAfterRender(null, $table.DataTable()); }); @@ -871,30 +938,45 @@ function drawSizeTable(selected, context) { function drawStateTable(selected, context) { var $table = $('table[data-type="by-state"]'); - var primary = _.object(_.map(selected, function(result) { - return [result.candidate_id, result]; - })); + var primary = _.object( + _.map(selected, function(result) { + return [result.candidate_id, result]; + }) + ); $.getJSON( - buildUrl(selected, context, ['schedules', 'schedule_a', 'by_state', 'by_candidate']) + buildUrl(selected, context, [ + 'schedules', + 'schedule_a', + 'by_state', + 'by_candidate' + ]) ).done(function(response) { var data = mapState(response, primary); // Populate headers with correct text var headerLabels = ['State'].concat(_.pluck(selected, 'candidate_name')); - $table.find('thead tr') + $table + .find('thead tr') .empty() - .append(_.map(headerLabels, function(label) { - return $('<th>').text(label); - })); + .append( + _.map(headerLabels, function(label) { + return $('<th>').text(label); + }) + ); destroyTable($table); - $table.dataTable(_.extend({ - autoWidth: false, - data: data, - columns: columnHelpers.stateColumns(selected, context), - order: [[1, 'desc']], - drawCallback: function(settings, $table) { - barsAfterRender(null, this.api()); - } - }, drawTableOpts)); + $table.dataTable( + _.extend( + { + autoWidth: false, + data: data, + columns: columnHelpers.stateColumns(selected, context), + order: [[1, 'desc']], + drawCallback: function(settings, $table) { + barsAfterRender(null, this.api()); + } + }, + drawTableOpts + ) + ); }); } diff --git a/fec/fec/static/js/modules/toc.js b/fec/fec/static/js/modules/toc.js index 5d9d846312..d57af69226 100644 --- a/fec/fec/static/js/modules/toc.js +++ b/fec/fec/static/js/modules/toc.js @@ -35,7 +35,7 @@ TOC.prototype.addWatchers = function() { return this.sections.map(function(idx, section) { var elm = document.querySelector(section); - var watcher = scrollMonitor.create(elm, {top: self.offset}); + var watcher = scrollMonitor.create(elm, { top: self.offset }); watcher.$menuItem = self.$menu.find('a[href="' + section + '"]'); watcher.enterViewport(function() { self.highlightActiveItem(this); diff --git a/fec/fec/static/js/modules/toggle.js b/fec/fec/static/js/modules/toggle.js index 32bbbb34c5..6c23b38d26 100644 --- a/fec/fec/static/js/modules/toggle.js +++ b/fec/fec/static/js/modules/toggle.js @@ -6,7 +6,7 @@ var $ = require('jquery'); module.exports = { init: function() { $(document).ready(function() { - $('.js-toggles input').each(function(){ + $('.js-toggles input').each(function() { $(this).attr('aria-controls', $(this).attr('value')); }); @@ -14,9 +14,9 @@ module.exports = { var $elm = $(e.target); $('[name="' + $elm.attr('name') + '"]').each(function(idx, input) { var $input = $(input); - $('#' + $input.attr('value')).attr('aria-hidden','true'); + $('#' + $input.attr('value')).attr('aria-hidden', 'true'); }); - $('#' + $elm.attr('value')).attr('aria-hidden','false'); + $('#' + $elm.attr('value')).attr('aria-hidden', 'false'); }); }); } diff --git a/fec/fec/static/js/modules/top-entities.js b/fec/fec/static/js/modules/top-entities.js index ded3559ffb..4de99eaf1f 100644 --- a/fec/fec/static/js/modules/top-entities.js +++ b/fec/fec/static/js/modules/top-entities.js @@ -24,9 +24,15 @@ function TopEntities(elm, type) { this.init(); $('.js-cycle').on('change', this.handleCycleChange.bind(this)); - this.$elm.find('.js-category').on('change', this.handleCategoryChange.bind(this)); - this.$elm.find('.js-previous').on('click', this.handlePagination.bind(this, 'previous')); - this.$elm.find('.js-next').on('click', this.handlePagination.bind(this, 'next')); + this.$elm + .find('.js-category') + .on('change', this.handleCategoryChange.bind(this)); + this.$elm + .find('.js-previous') + .on('click', this.handlePagination.bind(this, 'previous')); + this.$elm + .find('.js-next') + .on('click', this.handlePagination.bind(this, 'next')); } TopEntities.prototype.init = function() { @@ -41,7 +47,12 @@ TopEntities.prototype.init = function() { sort_hide_null: true, cycle: this.cycle }; - this.maxValue = Number(this.$table.find('.value-bar').first().data('value')); + this.maxValue = Number( + this.$table + .find('.value-bar') + .first() + .data('value') + ); // Store the current query for use in pagination and more this.currentQuery = this.baseQuery; @@ -64,7 +75,7 @@ TopEntities.prototype.handleCycleChange = function(e) { e.preventDefault(); this.cycle = e.target.value; if (this.category === 'candidates') { - this.currentQuery = _.extend({}, this.baseQuery, { + this.currentQuery = _.extend({}, this.baseQuery, { cycle: this.cycle, office: this.office, page: 1 @@ -91,7 +102,7 @@ TopEntities.prototype.handleCategoryChange = function(e) { cycle: this.cycle, page: 1 }); - } else { + } else { this.basePath = ['totals', category]; this.category = category; this.currentQuery = _.extend({}, this.baseQuery, { @@ -103,7 +114,9 @@ TopEntities.prototype.handleCategoryChange = function(e) { }; TopEntities.prototype.handlePagination = function(direction, e) { - if ($(e.target).hasClass('is-disabled')) { return; } + if ($(e.target).hasClass('is-disabled')) { + return; + } var currentPage = this.currentQuery.page || 1; if (direction === 'next') { this.currentQuery.page = currentPage + 1; @@ -116,9 +129,7 @@ TopEntities.prototype.handlePagination = function(direction, e) { TopEntities.prototype.loadData = function(query) { var self = this; - $.getJSON( - helpers.buildUrl(this.basePath, query) - ).done(function(response) { + $.getJSON(helpers.buildUrl(this.basePath, query)).done(function(response) { self.populateTable(response); }); }; @@ -176,7 +187,7 @@ TopEntities.prototype.formatData = function(result, rank) { TopEntities.prototype.drawBars = function() { var maxValue = this.maxValue; - this.$table.find('.value-bar').each(function(){ + this.$table.find('.value-bar').each(function() { var width = Number(this.getAttribute('data-value')) / maxValue; this.style.width = String(width * 100) + '%'; }); @@ -185,31 +196,34 @@ TopEntities.prototype.drawBars = function() { TopEntities.prototype.updateDates = function() { var today = new Date(); var startDate = '01/01/' + String(this.cycle - 1); - var endDate = this.cycle !== today.getFullYear() ? '12/31/' + this.cycle : moment(today).format('MM/DD/YYYY'); + var endDate = + this.cycle !== today.getFullYear() + ? '12/31/' + this.cycle + : moment(today).format('MM/DD/YYYY'); this.$dates.html(startDate + '–' + endDate); }; TopEntities.prototype.updatePagination = function(pagination) { - var page = pagination.page; - var per_page = pagination.per_page; - var count = pagination.count.toLocaleString(); - var range_start = String(per_page * (page - 1) + 1); - var range_end = String((page - 1) * 10 + per_page); - var info = range_start + '-' + range_end + ' of ' + count; - - if (page === pagination.pages) { - this.$next.addClass('is-disabled'); - } else { - this.$next.removeClass('is-disabled'); - } - - if (page === 1) { - this.$previous.addClass('is-disabled'); - } else { - this.$previous.removeClass('is-disabled'); - } - - this.$pageInfo.html(info); + var page = pagination.page; + var per_page = pagination.per_page; + var count = pagination.count.toLocaleString(); + var range_start = String(per_page * (page - 1) + 1); + var range_end = String((page - 1) * 10 + per_page); + var info = range_start + '-' + range_end + ' of ' + count; + + if (page === pagination.pages) { + this.$next.addClass('is-disabled'); + } else { + this.$next.removeClass('is-disabled'); + } + + if (page === 1) { + this.$previous.addClass('is-disabled'); + } else { + this.$previous.removeClass('is-disabled'); + } + + this.$pageInfo.html(info); }; -module.exports = {TopEntities: TopEntities}; +module.exports = { TopEntities: TopEntities }; diff --git a/fec/fec/static/js/modules/top-list.js b/fec/fec/static/js/modules/top-list.js index 82713cad5d..1ee72b47e3 100644 --- a/fec/fec/static/js/modules/top-list.js +++ b/fec/fec/static/js/modules/top-list.js @@ -55,7 +55,6 @@ TopList.prototype.showSpending = function() { this.zeroPad(); }; - module.exports = { TopList: TopList }; diff --git a/fec/fec/static/js/modules/typeahead.js b/fec/fec/static/js/modules/typeahead.js index 88f3f72a5f..4c6d921016 100644 --- a/fec/fec/static/js/modules/typeahead.js +++ b/fec/fec/static/js/modules/typeahead.js @@ -35,7 +35,7 @@ function formatCommittee(result) { id: result.id, type: 'committee' }; -} +} function formatAuditCommittee(result) { return { @@ -89,7 +89,7 @@ var committeeEngine = createEngine({ wildcard: '%QUERY', transform: function(response) { return _.map(response.results, formatCommittee); - }, + } } }); @@ -99,7 +99,7 @@ var auditCommitteeEngine = createEngine({ wildcard: '%QUERY', transform: function(response) { return _.map(response.results, formatAuditCommittee); - }, + } } }); @@ -109,7 +109,7 @@ var auditCandidateEngine = createEngine({ wildcard: '%QUERY', transform: function(response) { return _.map(response.results, formatAuditCandidate); - }, + } } }); @@ -120,13 +120,14 @@ var candidateDataset = { source: candidateEngine, templates: { header: '<span class="tt-suggestion__header">Select a candidate:</span>', - pending: '<span class="tt-suggestion__loading">Loading candidates...</span>', + pending: + '<span class="tt-suggestion__loading">Loading candidates...</span>', notFound: Handlebars.compile(''), // This has to be empty to not show anything suggestion: Handlebars.compile( '<span>' + - '<span class="tt-suggestion__name">{{ name }} ({{ id }})</span>' + - '<span class="tt-suggestion__office">{{ office }}</span>' + - '</span>' + '<span class="tt-suggestion__name">{{ name }} ({{ id }})</span>' + + '<span class="tt-suggestion__office">{{ office }}</span>' + + '</span>' ) } }; @@ -138,7 +139,8 @@ var committeeDataset = { source: committeeEngine, templates: { header: '<span class="tt-suggestion__header">Select a committee:</span>', - pending: '<span class="tt-suggestion__loading">Loading committees...</span>', + pending: + '<span class="tt-suggestion__loading">Loading committees...</span>', notFound: Handlebars.compile(''), // This has to be empty to not show anything suggestion: Handlebars.compile( '<span class="tt-suggestion__name">{{ name }} ({{ id }})</span>' @@ -146,7 +148,6 @@ var committeeDataset = { } }; - var auditCommitteeDataset = { name: 'auditCommittees', display: 'name', @@ -154,7 +155,8 @@ var auditCommitteeDataset = { source: auditCommitteeEngine, templates: { header: '<span class="tt-suggestion__header">Select a committee:</span>', - pending: '<span class="tt-suggestion__loading">Loading committees...</span>', + pending: + '<span class="tt-suggestion__loading">Loading committees...</span>', notFound: Handlebars.compile(''), // This has to be empty to not show anything suggestion: Handlebars.compile( '<span class="tt-suggestion__name">{{ name }} ({{ id }})</span>' @@ -169,7 +171,8 @@ var auditCandidateDataset = { source: auditCandidateEngine, templates: { header: '<span class="tt-suggestion__header">Select a candidate:</span>', - pending: '<span class="tt-suggestion__loading">Loading candidates...</span>', + pending: + '<span class="tt-suggestion__loading">Loading candidates...</span>', notFound: Handlebars.compile(''), // This has to be empty to not show anything suggestion: Handlebars.compile( '<span class="tt-suggestion__name">{{ name }} ({{ id }})</span>' @@ -184,15 +187,20 @@ var auditCandidateDataset = { var individualDataset = { display: 'id', source: function(query, syncResults) { - syncResults([{ - id: helpers.sanitizeValue(query), - type: 'individual' - }]); + syncResults([ + { + id: helpers.sanitizeValue(query), + type: 'individual' + } + ]); }, templates: { suggestion: function(datum) { - return '<span><strong>Search individual contributions from:</strong> "' + datum.id + - '"</span>'; + return ( + '<span><strong>Search individual contributions from:</strong> "' + + datum.id + + '"</span>' + ); } } }; @@ -203,14 +211,18 @@ var individualDataset = { var siteDataset = { display: 'id', source: function(query, syncResults) { - syncResults([{ - id: helpers.sanitizeValue(query), - type: 'site' - }]); + syncResults([ + { + id: helpers.sanitizeValue(query), + type: 'site' + } + ]); }, templates: { suggestion: function(datum) { - return '<span><strong>Search other pages:</strong> "' + datum.id + '"</span>'; + return ( + '<span><strong>Search other pages:</strong> "' + datum.id + '"</span>' + ); } } }; @@ -259,7 +271,10 @@ Typeahead.prototype.handleChangeEvent = function(data) { Typeahead.prototype.select = function(event, datum) { if (datum.type === 'individual') { - window.location = this.url + 'receipts/individual-contributions/?contributor_name=' + datum.id; + window.location = + this.url + + 'receipts/individual-contributions/?contributor_name=' + + datum.id; } else if (datum.type === 'site') { this.searchSite(datum.id); } else { diff --git a/fec/fec/static/js/modules/urls.js b/fec/fec/static/js/modules/urls.js index 8575f45415..82cf45c15a 100644 --- a/fec/fec/static/js/modules/urls.js +++ b/fec/fec/static/js/modules/urls.js @@ -9,7 +9,11 @@ var helpers = require('./helpers'); function updateQuery(params, fields) { var queryString = nextUrl(params, fields); if (queryString !== null) { - window.history.replaceState(params, queryString, queryString || window.location.pathname); + window.history.replaceState( + params, + queryString, + queryString || window.location.pathname + ); analytics.pageView(); } } @@ -17,20 +21,28 @@ function updateQuery(params, fields) { function pushQuery(params, fields) { var queryString = nextUrl(params, fields); if (queryString !== null) { - window.history.pushState(params, queryString, queryString || window.location.pathname); + window.history.pushState( + params, + queryString, + queryString || window.location.pathname + ); analytics.pageView(); } } function nextUrl(params, fields) { - var query = helpers.sanitizeQueryParams(URI.parseQuery(window.location.search)); + var query = helpers.sanitizeQueryParams( + URI.parseQuery(window.location.search) + ); if (!compareQuery(query, params, fields)) { // Clear and update filter fields _.each(fields, function(field) { delete query[field]; }); params = _.extend(query, params); - return URI('').query(params).toString(); + return URI('') + .query(params) + .toString(); } else { return null; } diff --git a/fec/fec/static/js/pages/calendar-page.js b/fec/fec/static/js/pages/calendar-page.js index 8a8a81cef6..04df4d0bcc 100644 --- a/fec/fec/static/js/pages/calendar-page.js +++ b/fec/fec/static/js/pages/calendar-page.js @@ -12,7 +12,7 @@ var filterPanel = new FilterPanel(); // Initialize filter tags var $tagList = new filterTags.TagList({ resultType: 'events', - emptyText: 'all events', + emptyText: 'all events' }).$body; $('.js-filter-tags').prepend($tagList); @@ -24,5 +24,5 @@ new Calendar({ subscribe: '#calendar-subscribe', url: calendarHelpers.getUrl(['calendar-dates']), exportUrl: calendarHelpers.getUrl(['calendar-dates', 'export']), - filterPanel: filterPanel, + filterPanel: filterPanel }); diff --git a/fec/fec/static/js/pages/candidate-single.js b/fec/fec/static/js/pages/candidate-single.js index c41e1cb34d..6087a49e9b 100644 --- a/fec/fec/static/js/pages/candidate-single.js +++ b/fec/fec/static/js/pages/candidate-single.js @@ -15,7 +15,7 @@ var events = require('../modules/events'); var OtherSpendingTotals = require('../modules/other-spending-totals'); var aggregateCallbacks = { - afterRender: tables.barsAfterRender.bind(undefined, undefined), + afterRender: tables.barsAfterRender.bind(undefined, undefined) }; // DOM element and URL for building the state map @@ -35,10 +35,14 @@ var expenditureColumns = [ className: 'all', orderable: true, orderSequence: ['desc', 'asc'], - render: columnHelpers.buildTotalLink(['independent-expenditures'], function(data, type, row) { + render: columnHelpers.buildTotalLink(['independent-expenditures'], function( + data, + type, + row + ) { return { support_oppose_indicator: row.support_oppose_indicator, - candidate_id: row.candidate_id, + candidate_id: row.candidate_id }; }) }, @@ -55,10 +59,14 @@ var communicationCostColumns = [ className: 'all', orderable: true, orderSequence: ['desc', 'asc'], - render: columnHelpers.buildTotalLink(['communication-costs'], function(data, type, row) { + render: columnHelpers.buildTotalLink(['communication-costs'], function( + data, + type, + row + ) { return { support_oppose_indicator: row.support_oppose_indicator, - candidate_id: row.candidate_id, + candidate_id: row.candidate_id }; }) }, @@ -75,12 +83,14 @@ var electioneeringColumns = [ className: 'all', orderable: true, orderSequence: ['desc', 'asc'], - render: columnHelpers.buildTotalLink(['electioneering-communications'], + render: columnHelpers.buildTotalLink( + ['electioneering-communications'], function(data, type, row) { return { candidate_id: row.candidate_id }; - }) + } + ) }, columns.committeeColumn({ data: 'committee', @@ -101,11 +111,15 @@ var otherDocumentsColumns = [ render: function(data, type, row) { var version = helpers.amendmentVersion(data); if (version === 'Version unknown') { - return '<i class="icon-blank"></i>Version unknown<br>' + - '<i class="icon-blank"></i>' + row.fec_file_id; + return ( + '<i class="icon-blank"></i>Version unknown<br>' + + '<i class="icon-blank"></i>' + + row.fec_file_id + ); } else { if (row.fec_file_id !== null) { - version = version + '<br><i class="icon-blank"></i>' + row.fec_file_id; + version = + version + '<br><i class="icon-blank"></i>' + row.fec_file_id; } return version; } @@ -133,12 +147,12 @@ var itemizedDisbursementColumns = [ { data: 'recipient_name', className: 'all', - orderable: false, + orderable: false }, { data: 'recipient_state', className: 'min-tablet hide-panel', - orderable: false, + orderable: false }, { data: 'disbursement_description', @@ -153,14 +167,14 @@ var itemizedDisbursementColumns = [ columns.currencyColumn({ data: 'disbursement_amount', className: 'column--number' - }), + }) ]; var individualContributionsColumns = [ { data: 'contributor_name', className: 'all', - orderable: false, + orderable: false }, { data: 'committee', @@ -182,7 +196,7 @@ var individualContributionsColumns = [ columns.currencyColumn({ data: 'contribution_receipt_amount', className: 'column--number' - }), + }) ]; // Begin datatable functions in order of tab appearance @@ -207,13 +221,13 @@ function initOtherDocumentsTable() { path: path, query: { candidate_id: candidateId, - form_type: ['F99','RFAI'], + form_type: ['F99', 'RFAI'], /* Performing an include would only show RFAI form types. For this reason, excludes need to be used for request_type Exclude all request types except for: // RQ-5: RFAI referencing Statement of Candidacy */ - request_type: ['-1','-2','-3','-4','-6','-7','-8','-9'], + request_type: ['-1', '-2', '-3', '-4', '-6', '-7', '-8', '-9'], sort_hide_null: ['false'] }, columns: otherDocumentsColumns, @@ -236,7 +250,7 @@ var tableOpts = { columns: communicationCostColumns, title: 'communication costs' }, - 'electioneering': { + electioneering: { path: ['electioneering', 'by_candidate'], columns: electioneeringColumns, title: 'electioneering communications' @@ -253,9 +267,12 @@ function initSpendingTables() { cycle: $table.data('cycle'), election_full: $table.data('election-full') }; - var displayCycle = helpers.formatCycleRange($table.data('cycle'), $table.data('duration')); - if(displayCycle == null) { - displayCycle = "unspecified cycle"; + var displayCycle = helpers.formatCycleRange( + $table.data('cycle'), + $table.data('duration') + ); + if (displayCycle == null) { + displayCycle = 'unspecified cycle'; } if (opts) { tables.DataTable.defer($table, { @@ -285,8 +302,8 @@ function initDisbursementsTable() { var $table = $('table[data-type="itemized-disbursements"]'); var path = ['schedules', 'schedule_b']; var committeeIdData = $table.data('committee-id'); - var committeeIds = ""; - if(committeeIdData) { + var committeeIds = ''; + if (committeeIdData) { committeeIds = committeeIdData.split(',').filter(Boolean); } var opts = { @@ -296,9 +313,12 @@ function initDisbursementsTable() { name: $table.data('name'), cycle: $table.data('cycle') }; - var displayCycle = helpers.formatCycleRange($table.data('cycle'), $table.data('duration')); - if(displayCycle == null) { - displayCycle = "unspecified cycle"; + var displayCycle = helpers.formatCycleRange( + $table.data('cycle'), + $table.data('duration') + ); + if (displayCycle == null) { + displayCycle = 'unspecified cycle'; } tables.DataTable.defer($table, { path: path, @@ -329,11 +349,14 @@ function initContributionsTables() { var $allTransactions = $('table[data-type="individual-contributions"]'); var $contributionSize = $('table[data-type="contribution-size"]'); var $contributorState = $('table[data-type="contributor-state"]'); - var displayCycle = helpers.formatCycleRange($allTransactions.data('cycle'), 2); + var displayCycle = helpers.formatCycleRange( + $allTransactions.data('cycle'), + 2 + ); var candidateName = $allTransactions.data('name'); var committeeIdData = $allTransactions.data('committee-id'); - var committeeIds = ""; - if(committeeIdData) { + var committeeIds = ''; + if (committeeIdData) { committeeIds = committeeIdData.split(',').filter(Boolean); } var opts = { @@ -343,7 +366,7 @@ function initContributionsTables() { candidate_id: $allTransactions.data('candidate-id'), title: 'individual contributions', name: candidateName, - cycle: $allTransactions.data('cycle'), + cycle: $allTransactions.data('cycle') }; var reason = helpers.missingDataReason('contributions'); @@ -380,24 +403,26 @@ function initContributionsTables() { sort_hide_null: false, per_page: 99 }, - columns: [{ - data: 'state_full', - width: '50%', - className: 'all', - render: function(data, type, row, meta) { - var span = document.createElement('span'); - span.textContent = data; - span.setAttribute('data-state', data); - span.setAttribute('data-row', meta.row); - return span.outerHTML; - } - }, + columns: [ + { + data: 'state_full', + width: '50%', + className: 'all', + render: function(data, type, row, meta) { + var span = document.createElement('span'); + span.textContent = data; + span.setAttribute('data-state', data); + span.setAttribute('data-row', meta.row); + return span.outerHTML; + } + }, { data: 'total', width: '50%', className: 'all', orderSequence: ['desc', 'asc'], - render: columnHelpers.buildTotalLink(['receipts', 'individual-contributions'], + render: columnHelpers.buildTotalLink( + ['receipts', 'individual-contributions'], function(data, type, row) { return { contributor_state: row.state, @@ -405,7 +430,8 @@ function initContributionsTables() { }; } ) - }], + } + ], callbacks: aggregateCallbacks, dom: 't', order: [[1, 'desc']], @@ -421,29 +447,32 @@ function initContributionsTables() { cycle: opts.cycle, sort: 'size' }, - columns: [{ - data: 'size', - width: '50%', - className: 'all', - orderable: false, - render: function(data) { - return columnHelpers.sizeInfo[data].label; - } - }, + columns: [ + { + data: 'size', + width: '50%', + className: 'all', + orderable: false, + render: function(data) { + return columnHelpers.sizeInfo[data].label; + } + }, { data: 'total', width: '50%', className: 'all', orderSequence: ['desc', 'asc'], orderable: false, - render: columnHelpers.buildTotalLink(['receipts', 'individual-contributions'], + render: columnHelpers.buildTotalLink( + ['receipts', 'individual-contributions'], function(data, type, row) { var params = columnHelpers.getSizeParams(row.size); params.committee_id = opts.committee_id; return params; } ) - }], + } + ], callbacks: aggregateCallbacks, dom: 't', order: false, @@ -456,7 +485,7 @@ function initContributionsTables() { email: WEBMANAGER_EMAIL, name: candidateName, timePeriod: displayCycle, - reason: reason, + reason: reason } }); diff --git a/fec/fec/static/js/pages/committee-single.js b/fec/fec/static/js/pages/committee-single.js index 07a8893708..0d6d853c4c 100644 --- a/fec/fec/static/js/pages/committee-single.js +++ b/fec/fec/static/js/pages/committee-single.js @@ -42,9 +42,12 @@ var sizeColumns = [ className: 'all', orderSequence: ['desc', 'asc'], orderable: false, - render: columnHelpers.buildTotalLink(['receipts', 'individual-contributions'], function(data, type, row, meta) { - return columnHelpers.getSizeParams(row.size); - }) + render: columnHelpers.buildTotalLink( + ['receipts', 'individual-contributions'], + function(data, type, row, meta) { + return columnHelpers.getSizeParams(row.size); + } + ) } ]; @@ -66,12 +69,15 @@ var stateColumns = [ width: '50%', className: 'all', orderSequence: ['desc', 'asc'], - render: columnHelpers.buildTotalLink(['receipts', 'individual-contributions'], function(data, type, row, meta) { - return { - contributor_state: row.state, - }; - }) - }, + render: columnHelpers.buildTotalLink( + ['receipts', 'individual-contributions'], + function(data, type, row, meta) { + return { + contributor_state: row.state + }; + } + ) + } ]; var employerColumns = [ @@ -86,15 +92,18 @@ var employerColumns = [ className: 'all', orderable: false, orderSequence: ['desc', 'asc'], - render: columnHelpers.buildTotalLink(['receipts', 'individual-contributions'], function(data, type, row, meta) { - if (row.employer) { - return { - contributor_employer: row.employer, - }; - } else { - return null; + render: columnHelpers.buildTotalLink( + ['receipts', 'individual-contributions'], + function(data, type, row, meta) { + if (row.employer) { + return { + contributor_employer: row.employer + }; + } else { + return null; + } } - }) + ) } ]; @@ -110,15 +119,18 @@ var occupationColumns = [ className: 'all', orderable: false, orderSequence: ['desc', 'asc'], - render: columnHelpers.buildTotalLink(['receipts', 'individual-contributions'], function(data, type, row, meta) { - if (row.occupation) { - return { - contributor_occupation: row.occupation, - }; - } else { - return null; + render: columnHelpers.buildTotalLink( + ['receipts', 'individual-contributions'], + function(data, type, row, meta) { + if (row.occupation) { + return { + contributor_occupation: row.occupation + }; + } else { + return null; + } } - }) + ) } ]; @@ -133,7 +145,12 @@ var disbursementRecipientColumns = [ className: 'all', orderable: false, orderSequence: ['desc', 'asc'], - render: columnHelpers.buildTotalLink(['disbursements'], function(data, type, row, meta) { + render: columnHelpers.buildTotalLink(['disbursements'], function( + data, + type, + row, + meta + ) { return { recipient_name: row.recipient_name }; @@ -159,7 +176,12 @@ var disbursementRecipientIDColumns = [ className: 'all', orderable: false, orderSequence: ['desc', 'asc'], - render: columnHelpers.buildTotalLink(['disbursements'], function(data, type, row, meta) { + render: columnHelpers.buildTotalLink(['disbursements'], function( + data, + type, + row, + meta + ) { return { recipient_name: row.recipient_id }; @@ -173,11 +195,16 @@ var expendituresColumns = [ className: 'all', orderable: true, orderSequence: ['desc', 'asc'], - render: columnHelpers.buildTotalLink(['independent-expenditures'], function(data, type, row, meta) { + render: columnHelpers.buildTotalLink(['independent-expenditures'], function( + data, + type, + row, + meta + ) { return { support_oppose_indicator: row.support_oppose_indicator, - candidate_id: row.candidate_id, - // is_notice: false, + candidate_id: row.candidate_id + // is_notice: false, }; }) }, @@ -194,12 +221,15 @@ var electioneeringColumns = [ className: 'all', orderable: true, orderSequence: ['desc', 'asc'], - render: columnHelpers.buildTotalLink(['electioneering-communications'], function(data, type, row, meta) { - return { - support_oppose_indicator: row.support_oppose_indicator, - candidate_id: row.candidate_id, - }; - }) + render: columnHelpers.buildTotalLink( + ['electioneering-communications'], + function(data, type, row, meta) { + return { + support_oppose_indicator: row.support_oppose_indicator, + candidate_id: row.candidate_id + }; + } + ) }, columns.candidateColumn({ data: 'candidate', @@ -213,10 +243,15 @@ var communicationCostColumns = [ className: 'all', orderable: true, orderSequence: ['desc', 'asc'], - render: columnHelpers.buildTotalLink(['communication-costs'], function(data, type, row, meta) { + render: columnHelpers.buildTotalLink(['communication-costs'], function( + data, + type, + row, + meta + ) { return { support_oppose_indicator: row.support_oppose_indicator, - candidate_id: row.candidate_id, + candidate_id: row.candidate_id }; }) }, @@ -231,12 +266,12 @@ var itemizedDisbursementColumns = [ { data: 'recipient_name', className: 'all', - orderable: false, + orderable: false }, { data: 'recipient_state', className: 'min-tablet hide-panel', - orderable: false, + orderable: false }, { data: 'disbursement_description', @@ -251,19 +286,19 @@ var itemizedDisbursementColumns = [ columns.currencyColumn({ data: 'disbursement_amount', className: 'column--number' - }), + }) ]; var individualContributionsColumns = [ { data: 'contributor_name', className: 'all', - orderable: false, + orderable: false }, { data: 'contributor_state', className: 'all', - orderable: false, + orderable: false }, columns.dateColumn({ data: 'contribution_receipt_date', @@ -272,29 +307,37 @@ var individualContributionsColumns = [ columns.currencyColumn({ data: 'contribution_receipt_amount', className: 'column--number' - }), + }) ]; - var aggregateCallbacks = { - afterRender: tables.barsAfterRender.bind(undefined, undefined), + afterRender: tables.barsAfterRender.bind(undefined, undefined) }; // Settings for filings tables -var rawFilingsColumns = columnHelpers.getColumns( - columns.filings, - ['document_type', 'coverage_start_date', 'coverage_end_date', 'receipt_date'] -); - -var filingsColumns = columnHelpers.getColumns( - columns.filings, - ['document_type', 'version', 'receipt_date', 'pages'] -); - -var filingsReportsColumns = columnHelpers.getColumns( - columns.filings, - ['document_type', 'version', 'coverage_start_date', 'coverage_end_date', 'receipt_date_unorderable', 'pages', 'modal_trigger'] -); +var rawFilingsColumns = columnHelpers.getColumns(columns.filings, [ + 'document_type', + 'coverage_start_date', + 'coverage_end_date', + 'receipt_date' +]); + +var filingsColumns = columnHelpers.getColumns(columns.filings, [ + 'document_type', + 'version', + 'receipt_date', + 'pages' +]); + +var filingsReportsColumns = columnHelpers.getColumns(columns.filings, [ + 'document_type', + 'version', + 'coverage_start_date', + 'coverage_end_date', + 'receipt_date_unorderable', + 'pages', + 'modal_trigger' +]); $(document).ready(function() { var $mapTable; @@ -347,7 +390,13 @@ $(document).ready(function() { }); break; case 'receipts-by-state': - path = ['committee', committeeId, 'schedules', 'schedule_a', 'by_state']; + path = [ + 'committee', + committeeId, + 'schedules', + 'schedule_a', + 'by_state' + ]; query = _.extend(query, { per_page: 99 }); @@ -368,7 +417,13 @@ $(document).ready(function() { break; case 'receipts-by-employer': - path = ['committee', committeeId, 'schedules', 'schedule_a', 'by_employer']; + path = [ + 'committee', + committeeId, + 'schedules', + 'schedule_a', + 'by_employer' + ]; tables.DataTable.defer( $table, _.extend({}, tableOpts, { @@ -381,13 +436,19 @@ $(document).ready(function() { dataType: 'individual contributions', name: context.name, timePeriod: context.timePeriod, - reason: helpers.missingDataReason('contributions'), - }, + reason: helpers.missingDataReason('contributions') + } }) ); break; case 'receipts-by-occupation': - path = ['committee', committeeId, 'schedules', 'schedule_a', 'by_occupation']; + path = [ + 'committee', + committeeId, + 'schedules', + 'schedule_a', + 'by_occupation' + ]; tables.DataTable.defer( $table, _.extend({}, tableOpts, { @@ -400,8 +461,8 @@ $(document).ready(function() { dataType: 'individual contributions', name: context.name, timePeriod: context.timePeriod, - reason: helpers.missingDataReason('contributions'), - }, + reason: helpers.missingDataReason('contributions') + } }) ); break; @@ -414,7 +475,7 @@ $(document).ready(function() { query: { committee_id: committeeId, two_year_transaction_period: cycle, - is_individual: true, + is_individual: true }, columns: individualContributionsColumns, callbacks: aggregateCallbacks, @@ -426,13 +487,19 @@ $(document).ready(function() { dataType: 'individual contributions', name: context.name, timePeriod: context.timePeriod, - reason: helpers.missingDataReason('contributions'), - }, + reason: helpers.missingDataReason('contributions') + } }) ); break; case 'disbursements-by-recipient': - path = ['committee', committeeId, 'schedules', 'schedule_b', 'by_recipient']; + path = [ + 'committee', + committeeId, + 'schedules', + 'schedule_b', + 'by_recipient' + ]; tables.DataTable.defer( $table, _.extend({}, tableOpts, { @@ -446,7 +513,7 @@ $(document).ready(function() { name: context.name, reason: helpers.missingDataReason('disbursements'), timePeriod: context.timePeriod - }, + } }) ); break; @@ -471,12 +538,18 @@ $(document).ready(function() { name: context.name, reason: helpers.missingDataReason('disbursements'), timePeriod: context.timePeriod - }, + } }) ); break; case 'disbursements-by-recipient-id': - path = ['committee', committeeId, 'schedules', 'schedule_b', 'by_recipient_id']; + path = [ + 'committee', + committeeId, + 'schedules', + 'schedule_b', + 'by_recipient_id' + ]; tables.DataTable.defer( $table, _.extend({}, tableOpts, { @@ -490,12 +563,18 @@ $(document).ready(function() { name: context.name, reason: helpers.missingDataReason('disbursements'), timePeriod: context.timePeriod - }, + } }) ); break; case 'independent-expenditure-committee': - path = ['committee', committeeId, 'schedules', 'schedule_e', 'by_candidate']; + path = [ + 'committee', + committeeId, + 'schedules', + 'schedule_e', + 'by_candidate' + ]; tables.DataTable.defer($table, { path: path, query: query, @@ -509,7 +588,7 @@ $(document).ready(function() { name: context.name, reason: helpers.missingDataReason('ie-made'), timePeriod: context.timePeriod - }, + } }); break; case 'electioneering-committee': @@ -526,11 +605,16 @@ $(document).ready(function() { dataType: 'electioneering communications', name: context.name, timePeriod: context.timePeriod - }, + } }); break; case 'communication-cost-committee': - path = ['committee', committeeId, 'communication_costs', 'by_candidate']; + path = [ + 'committee', + committeeId, + 'communication_costs', + 'by_candidate' + ]; tables.DataTable.defer($table, { path: path, query: query, @@ -543,35 +627,53 @@ $(document).ready(function() { dataType: 'communication costs', name: context.name, timePeriod: context.timePeriod - }, + } }); break; case 'raw-filings': var min_date = $table.attr('data-min-date'); - opts = _.extend({ - columns: rawFilingsColumns, - order: [[1, 'desc']], - path: ['efile', 'filings'], - query: _.extend({ - committee_id: committeeId, - min_receipt_date: min_date, - sort: ['-receipt_date'] - }, query), - callbacks: { - afterRender: filings.renderModal - } - }, filingsOpts); + opts = _.extend( + { + columns: rawFilingsColumns, + order: [[1, 'desc']], + path: ['efile', 'filings'], + query: _.extend( + { + committee_id: committeeId, + min_receipt_date: min_date, + sort: ['-receipt_date'] + }, + query + ), + callbacks: { + afterRender: filings.renderModal + } + }, + filingsOpts + ); tables.DataTable.defer($table, opts); break; case 'filings-reports': - opts = _.extend({ - columns: filingsReportsColumns, - order: [], - path: ['committee', committeeId, 'filings'], - query: _.extend({ - form_type: ['F3', 'F3X', 'F3P', 'F3L', 'F4', 'F5', 'F7', 'F13', 'RFAI'], - report_type: ['-24', '-48'], - /* Performing an include would only show RFAI form types. For this reason, excludes need to be + opts = _.extend( + { + columns: filingsReportsColumns, + order: [], + path: ['committee', committeeId, 'filings'], + query: _.extend( + { + form_type: [ + 'F3', + 'F3X', + 'F3P', + 'F3L', + 'F4', + 'F5', + 'F7', + 'F13', + 'RFAI' + ], + report_type: ['-24', '-48'], + /* Performing an include would only show RFAI form types. For this reason, excludes need to be used for request_type Exclude all request types except for: @@ -580,25 +682,35 @@ $(document).ready(function() { - RQ-4: RFAI referencing Independent Expenditure filer - RQ-7: RFAI referencing failure to file - RQ-8: RFAI referencing public disclosure */ - request_type: ['-1','-5','-6','-9'], - sort: ['-coverage_end_date', 'report_type_full', '-beginning_image_number'], - sort_hide_null: ['false'] - }, query), - callbacks: { - afterRender: filings.renderModal - } - }, filingsOpts); + request_type: ['-1', '-5', '-6', '-9'], + sort: [ + '-coverage_end_date', + 'report_type_full', + '-beginning_image_number' + ], + sort_hide_null: ['false'] + }, + query + ), + callbacks: { + afterRender: filings.renderModal + } + }, + filingsOpts + ); tables.DataTable.defer($table, opts); break; case 'filings-notices': - opts = _.extend({ - columns: filingsColumns, - order: [[2, 'desc']], - path: ['committee', committeeId, 'filings'], - query: _.extend({ - form_type: ['F5', 'F24', 'F6', 'F9', 'F10', 'F11', 'RFAI'], - report_type: ['-Q1', '-Q2', '-Q3', '-YE'], - /* Performing an include would only show RFAI form types. For this reason, excludes need to be + opts = _.extend( + { + columns: filingsColumns, + order: [[2, 'desc']], + path: ['committee', committeeId, 'filings'], + query: _.extend( + { + form_type: ['F5', 'F24', 'F6', 'F9', 'F10', 'F11', 'RFAI'], + report_type: ['-Q1', '-Q2', '-Q3', '-YE'], + /* Performing an include would only show RFAI form types. For this reason, excludes need to be used for request_type Exclude all request types except for: @@ -607,47 +719,63 @@ $(document).ready(function() { Exclude quarterly report_types so F5 quarterlies don't appear */ - request_type: ['-1', '-3', '-5', '-6', '-7', '-8', '-9'], - sort_hide_null: ['false'] - }, query), - }, filingsOpts); + request_type: ['-1', '-3', '-5', '-6', '-7', '-8', '-9'], + sort_hide_null: ['false'] + }, + query + ) + }, + filingsOpts + ); tables.DataTable.defer($table, opts); break; case 'filings-statements': - opts = _.extend({ - columns: filingsColumns, - order: [[2, 'desc']], - path: ['committee', committeeId, 'filings'], - query: _.extend({ - form_type: ['F1','RFAI'], - /* Performing an include would only show RFAI form types. For this reason, excludes need to be + opts = _.extend( + { + columns: filingsColumns, + order: [[2, 'desc']], + path: ['committee', committeeId, 'filings'], + query: _.extend( + { + form_type: ['F1', 'RFAI'], + /* Performing an include would only show RFAI form types. For this reason, excludes need to be used for request_type Exclude all request types except for: - RQ-1: RFAI referencing Statement of organization - RQ-6: RFAI referencing 2nd notice State of organization */ - request_type: ['-2','-3','-4','-5','-7','-8','-9'], - sort_hide_null: ['false'] - }, query), - }, filingsOpts); + request_type: ['-2', '-3', '-4', '-5', '-7', '-8', '-9'], + sort_hide_null: ['false'] + }, + query + ) + }, + filingsOpts + ); tables.DataTable.defer($table, opts); break; case 'filings-other': - opts = _.extend({ - columns: filingsColumns, - order: [[2, 'desc']], - path: ['committee', committeeId, 'filings'], - query: _.extend({ - form_type: ['F1M', 'F8', 'F99', 'F12','RFAI'], - /* Performing an include would only show RFAI form types. For this reason, excludes need to be + opts = _.extend( + { + columns: filingsColumns, + order: [[2, 'desc']], + path: ['committee', committeeId, 'filings'], + query: _.extend( + { + form_type: ['F1M', 'F8', 'F99', 'F12', 'RFAI'], + /* Performing an include would only show RFAI form types. For this reason, excludes need to be used for request_type Exclude all request types except for: - RQ-9: RFAI referencing Multicandidate status */ - request_type: ['-1','-2','-3','-4','-5','-6','-7','-8'], - sort_hide_null: ['false'] - }, query), - }, filingsOpts); + request_type: ['-1', '-2', '-3', '-4', '-5', '-6', '-7', '-8'], + sort_hide_null: ['false'] + }, + query + ) + }, + filingsOpts + ); tables.DataTable.defer($table, opts); break; } @@ -656,7 +784,13 @@ $(document).ready(function() { // Set up state map var $map = $('.state-map'); var mapUrl = helpers.buildUrl( - ['committee', $map.data('committee-id'), 'schedules', 'schedule_a', 'by_state'], + [ + 'committee', + $map.data('committee-id'), + 'schedules', + 'schedule_a', + 'by_state' + ], { cycle: $map.data('cycle'), per_page: 99 diff --git a/fec/fec/static/js/pages/contact-form.js b/fec/fec/static/js/pages/contact-form.js index e93fdc9cb3..cbc7095925 100644 --- a/fec/fec/static/js/pages/contact-form.js +++ b/fec/fec/static/js/pages/contact-form.js @@ -12,7 +12,11 @@ function ContactForm($elm) { this.committeeId = $elm.find('#id_u_committee'); this.category = $elm.find('#id_u_category'); this.otherReason = $elm.find('#id_u_other_reason').closest('div'); - this.typeahead = new Typeahead($elm.find('.js-contact-typeahead'), 'committees', ''); + this.typeahead = new Typeahead( + $elm.find('.js-contact-typeahead'), + 'committees', + '' + ); this.$cancel = $elm.find('.js-cancel'); this.initTypeahead(); this.initOtherReason(); @@ -24,7 +28,7 @@ ContactForm.prototype.initTypeahead = function() { // Overriding default typeahead behavior // This will set the value of a hidden input when selecting a value from typeahead var self = this; - this.typeahead.$element.css({'height': 'auto'}); + this.typeahead.$element.css({ height: 'auto' }); this.typeahead.$input.off('typeahead:select'); this.typeahead.$input.on('typeahead:select', function(e, opts) { self.committeeId.val(opts.id); @@ -70,20 +74,20 @@ function AnalystLookup($elm) { AnalystLookup.prototype.initTypeahead = function() { // Overriding default typeahead behavior - this.typeahead.$element.css({'height': 'auto'}); + this.typeahead.$element.css({ height: 'auto' }); this.typeahead.$input.off('typeahead:select'); this.typeahead.$input.on('typeahead:select', this.fetchAnalyst.bind(this)); }; AnalystLookup.prototype.fetchAnalyst = function(e, opts) { var url = URI(window.API_LOCATION) - .path(Array.prototype.concat(window.API_VERSION, 'rad-analyst').join('/')) - .addQuery({ - api_key: window.API_KEY, - per_page: 1, - committee_id: opts.id - }) - .toString(); + .path(Array.prototype.concat(window.API_VERSION, 'rad-analyst').join('/')) + .addQuery({ + api_key: window.API_KEY, + per_page: 1, + committee_id: opts.id + }) + .toString(); $.getJSON(url).done(this.showAnalyst.bind(this)); }; @@ -91,11 +95,12 @@ AnalystLookup.prototype.fetchAnalyst = function(e, opts) { AnalystLookup.prototype.showAnalyst = function(response) { var hasResults = response.results.length > 0; if (hasResults) { - var name = response.results[0].first_name + ' ' + response.results[0].last_name; + var name = + response.results[0].first_name + ' ' + response.results[0].last_name; var ext = response.results[0].telephone_ext; this.$name.html(name); this.$ext.html(ext); - } + } this.$analystContainer.attr('aria-hidden', 'false'); this.$prompt.attr('aria-hidden', 'true'); this.$analystDetails.attr('aria-hidden', !hasResults); @@ -124,4 +129,4 @@ new AnalystLookup($('.js-analyst-lookup')); module.exports = { AnalystLookup: AnalystLookup, ContactForm: ContactForm -}; \ No newline at end of file +}; diff --git a/fec/fec/static/js/pages/data-landing.js b/fec/fec/static/js/pages/data-landing.js index 7db7b667a9..d13c1525e4 100644 --- a/fec/fec/static/js/pages/data-landing.js +++ b/fec/fec/static/js/pages/data-landing.js @@ -29,13 +29,24 @@ function Overview(selector, type, index) { } Overview.prototype.init = function() { - if (this.initialized) { return; } - new LineChart(this.selector + ' .js-chart', this.selector + ' .js-snapshot', this.type, this.index); + if (this.initialized) { + return; + } + new LineChart( + this.selector + ' .js-chart', + this.selector + ' .js-snapshot', + this.type, + this.index + ); this.initialized = true; }; Overview.prototype.zeroPadTotals = function() { - helpers.zeroPad(this.selector + ' .js-snapshot', '.snapshot__item-number', '.figure__decimals'); + helpers.zeroPad( + this.selector + ' .js-snapshot', + '.snapshot__item-number', + '.figure__decimals' + ); }; //temporarily removed to remove line-charts from landng.jinja without error diff --git a/fec/fec/static/js/pages/datatable-audit.js b/fec/fec/static/js/pages/datatable-audit.js index 5e760a5a17..19e3ecbd14 100644 --- a/fec/fec/static/js/pages/datatable-audit.js +++ b/fec/fec/static/js/pages/datatable-audit.js @@ -1,11 +1,11 @@ 'use strict'; /** -* audit datatable page -* --------------------- -* inital show all audit case. -* -*/ + * audit datatable page + * --------------------- + * inital show all audit case. + * + */ var $ = require('jquery'); @@ -15,7 +15,6 @@ var tablePanels = require('../modules/table-panels'); var auditCategorySubcCategory = require('../modules/audit-category-sub-category'); var auditTags = require('../modules/audit_tags'); - $(document).ready(function() { var $table = $('#results'); new tables.DataTable($table, { @@ -28,7 +27,7 @@ $(document).ready(function() { useExport: true, rowCallback: tables.modalRenderRow, callbacks: { - // afterRender: tablePanels.renderauditPanel(false) + // afterRender: tablePanels.renderauditPanel(false) } }); }); diff --git a/fec/fec/static/js/pages/datatable-candidates-office.js b/fec/fec/static/js/pages/datatable-candidates-office.js index 29c617865d..faac1646ec 100644 --- a/fec/fec/static/js/pages/datatable-candidates-office.js +++ b/fec/fec/static/js/pages/datatable-candidates-office.js @@ -10,9 +10,30 @@ var columns = require('../modules/columns'); var tablePanels = require('../modules/table-panels'); var columnGroups = { - president: columnHelpers.getColumns(columns.candidateOffice, ['name', 'party', 'receipts', 'disbursements', 'trigger']), - senate: columnHelpers.getColumns(columns.candidateOffice, ['name', 'party', 'state', 'receipts', 'disbursements', 'trigger']), - house: columnHelpers.getColumns(columns.candidateOffice, ['name', 'party', 'state', 'district', 'receipts', 'disbursements', 'trigger']), + president: columnHelpers.getColumns(columns.candidateOffice, [ + 'name', + 'party', + 'receipts', + 'disbursements', + 'trigger' + ]), + senate: columnHelpers.getColumns(columns.candidateOffice, [ + 'name', + 'party', + 'state', + 'receipts', + 'disbursements', + 'trigger' + ]), + house: columnHelpers.getColumns(columns.candidateOffice, [ + 'name', + 'party', + 'state', + 'district', + 'receipts', + 'disbursements', + 'trigger' + ]) }; var defaultSort = { @@ -33,7 +54,7 @@ $(document).ready(function() { autoWidth: false, title: 'Candidates for ' + officeTitleMap[context.office], path: ['candidates', 'totals'], - query: {office: context.office.slice(0, 1).toUpperCase()}, + query: { office: context.office.slice(0, 1).toUpperCase() }, columns: columnGroups[context.office], order: [[defaultSort[context.office], 'desc']], useFilters: true, diff --git a/fec/fec/static/js/pages/datatable-filings.js b/fec/fec/static/js/pages/datatable-filings.js index bde10d3929..bafdfbf638 100644 --- a/fec/fec/static/js/pages/datatable-filings.js +++ b/fec/fec/static/js/pages/datatable-filings.js @@ -11,12 +11,14 @@ var tables = require('../modules/tables'); var TableSwitcher = require('../modules/table-switcher').TableSwitcher; var dropdown = require('../modules/dropdowns'); -var columns = columnHelpers.getColumns( - columns.filings, - [ - 'filer_name', 'document_type', 'version', 'receipt_date', 'beginning_image_number', 'modal_trigger' - ] -); +var columns = columnHelpers.getColumns(columns.filings, [ + 'filer_name', + 'document_type', + 'version', + 'receipt_date', + 'beginning_image_number', + 'modal_trigger' +]); $(document).ready(function() { var $table = $('#results'); @@ -34,9 +36,9 @@ $(document).ready(function() { callbacks: { afterRender: filings.renderModal }, - drawCallback: function () { + drawCallback: function() { this.dropdowns = $table.find('.dropdown').map(function(idx, elm) { - return new dropdown.Dropdown($(elm), {checkboxes: false}); + return new dropdown.Dropdown($(elm), { checkboxes: false }); }); } }); diff --git a/fec/fec/static/js/pages/datatable-individual-contributions.js b/fec/fec/static/js/pages/datatable-individual-contributions.js index 26555f30f7..aa073e775f 100644 --- a/fec/fec/static/js/pages/datatable-individual-contributions.js +++ b/fec/fec/static/js/pages/datatable-individual-contributions.js @@ -14,7 +14,7 @@ $(document).ready(function() { autoWidth: false, title: 'Individual contributions', path: ['schedules', 'schedule_a'], - query: {is_individual: true}, + query: { is_individual: true }, columns: columns.individualContributions, paginator: tables.SeekPaginator, order: [[4, 'desc']], diff --git a/fec/fec/static/js/pages/datatable-loans.js b/fec/fec/static/js/pages/datatable-loans.js index e28823efcb..e381c74015 100644 --- a/fec/fec/static/js/pages/datatable-loans.js +++ b/fec/fec/static/js/pages/datatable-loans.js @@ -1,12 +1,12 @@ 'use strict'; /** -* Loans datatable page -* --------------------- -* Schedule C shows loans to -* the committee that are required to be disclosed. -* -*/ + * Loans datatable page + * --------------------- + * Schedule C shows loans to + * the committee that are required to be disclosed. + * + */ var $ = require('jquery'); @@ -20,7 +20,7 @@ $(document).ready(function() { new tables.DataTable($table, { autoWidth: false, title: 'Loans', - path: ['schedules','schedule_c'], + path: ['schedules', 'schedule_c'], columns: columns.loans, order: [[2, 'desc']], useFilters: true, diff --git a/fec/fec/static/js/pages/datatable-party-coordinated-expenditures.js b/fec/fec/static/js/pages/datatable-party-coordinated-expenditures.js index bcd86e9b3b..5c160d5892 100644 --- a/fec/fec/static/js/pages/datatable-party-coordinated-expenditures.js +++ b/fec/fec/static/js/pages/datatable-party-coordinated-expenditures.js @@ -1,12 +1,12 @@ 'use strict'; /** -* Party Coordinated Expenditures datatable page -* --------------------- -* Schedule F shows all special expenditures a national or state party committee -* makes in connection with the general election campaigns of federal candidates. -* -*/ + * Party Coordinated Expenditures datatable page + * --------------------- + * Schedule F shows all special expenditures a national or state party committee + * makes in connection with the general election campaigns of federal candidates. + * + */ var $ = require('jquery'); @@ -27,7 +27,9 @@ $(document).ready(function() { useFilters: true, rowCallback: tables.modalRenderRow, callbacks: { - afterRender: tables.modalRenderFactory(partyCoordinatedExpendituresTemplate) + afterRender: tables.modalRenderFactory( + partyCoordinatedExpendituresTemplate + ) } }); }); diff --git a/fec/fec/static/js/pages/datatable-reports.js b/fec/fec/static/js/pages/datatable-reports.js index 6d84966c8c..363a81a7da 100644 --- a/fec/fec/static/js/pages/datatable-reports.js +++ b/fec/fec/static/js/pages/datatable-reports.js @@ -16,9 +16,15 @@ var pacPartyTemplate = require('../templates/reports/pac.hbs'); var ieOnlyTemplate = require('../templates/reports/ie-only.hbs'); var pageTitle, - pageTemplate, - pageColumns, - columnKeys = ['committee', 'document_type', 'version', 'receipt_date', 'coverage_end_date']; + pageTemplate, + pageColumns, + columnKeys = [ + 'committee', + 'document_type', + 'version', + 'receipt_date', + 'coverage_end_date' + ]; if (context.form_type === 'presidential') { pageTitle = 'Presidential committee reports'; @@ -31,17 +37,19 @@ if (context.form_type === 'presidential') { } else if (context.form_type === 'pac-party') { pageTitle = 'PAC and party committee reports'; pageTemplate = pacPartyTemplate; - columnKeys.push('receipts', 'disbursements', 'independentExpenditures', 'trigger'); + columnKeys.push( + 'receipts', + 'disbursements', + 'independentExpenditures', + 'trigger' + ); } else if (context.form_type === 'ie-only') { pageTitle = 'Independent expenditure only committee reports'; pageTemplate = ieOnlyTemplate; columnKeys.push('contributions', 'independentExpenditures', 'trigger'); } -pageColumns = columnHelpers.getColumns( - columns.reports, - columnKeys -); +pageColumns = columnHelpers.getColumns(columns.reports, columnKeys); $(document).ready(function() { var $table = $('#results'); @@ -57,11 +65,14 @@ $(document).ready(function() { useFilters: true, useExport: true, callbacks: { - afterRender: tables.modalRenderFactory(pageTemplate, filings.fetchReportDetails) + afterRender: tables.modalRenderFactory( + pageTemplate, + filings.fetchReportDetails + ) }, - drawCallback: function () { + drawCallback: function() { this.dropdowns = $table.find('.dropdown').map(function(idx, elm) { - return new dropdown.Dropdown($(elm), {checkboxes: false}); + return new dropdown.Dropdown($(elm), { checkboxes: false }); }); } }); diff --git a/fec/fec/static/js/pages/elections.js b/fec/fec/static/js/pages/elections.js index c6379d228c..4eed51cb17 100644 --- a/fec/fec/static/js/pages/elections.js +++ b/fec/fec/static/js/pages/elections.js @@ -19,7 +19,7 @@ $(document).ready(function() { path: ['schedules', 'schedule_e', 'by_candidate'], columns: tableColumns.independentExpenditureColumns, title: 'independent expenditures', - order: [[3, 'desc']], + order: [[3, 'desc']] }, 'communication-costs': { path: ['communication_costs', 'by_candidate'], @@ -27,7 +27,7 @@ $(document).ready(function() { title: 'communication costs', order: [[3, 'desc']] }, - 'electioneering': { + electioneering: { path: ['electioneering', 'by_candidate'], columns: tableColumns.electioneeringColumns, title: 'electioneering communications', @@ -44,13 +44,10 @@ $(document).ready(function() { columns: tableColumns.candidateInformationColumns, title: 'candidate information', order: [[3, 'desc']] - }, + } }; - var url = helpers.buildUrl( - ['elections'], - query - ); + var url = helpers.buildUrl(['elections'], query); $.getJSON(url).done(function(response) { context.candidates = _.chain(response.results) @@ -61,28 +58,28 @@ $(document).ready(function() { .value(); var incumbents = response.results.filter(function(result) { - return result.incumbent_challenge_full=='Incumbent'; + return result.incumbent_challenge_full == 'Incumbent'; }); tables.drawComparison(response.results, context); maps.initStateMaps(response.results); helpers.scrollAnchor(); - }); electionUtils.getStateElectionOffices(context.election.state); - electionUtils.getElections(context.election.state, context.election.office, context.election.cycle); + electionUtils.getElections( + context.election.state, + context.election.office, + context.election.cycle + ); tables.initSpendingTables('.data-table', context, spendingTableOpts); new ElectionForm('#election-nav'); if ($('#election-map').length) { - var districtMap = new maps.DistrictMap( - $('#election-map').get(0), - {color: '#36BDBB'} - ); + var districtMap = new maps.DistrictMap($('#election-map').get(0), { + color: '#36BDBB' + }); districtMap.load(context.election); } - - }); diff --git a/fec/fec/static/js/pages/legal.js b/fec/fec/static/js/pages/legal.js index 9416b0835e..31ce07c90e 100644 --- a/fec/fec/static/js/pages/legal.js +++ b/fec/fec/static/js/pages/legal.js @@ -5,4 +5,4 @@ var FilterPanel = require('../modules/filters/filter-panel').FilterPanel; var KeywordModal = require('../modules/keyword-modal').KeywordModal; new FilterPanel(); -new KeywordModal(); \ No newline at end of file +new KeywordModal(); diff --git a/fec/fec/static/js/vendor/beautify-html.js b/fec/fec/static/js/vendor/beautify-html.js index efc4061c04..9f556cdecb 100644 --- a/fec/fec/static/js/vendor/beautify-html.js +++ b/fec/fec/static/js/vendor/beautify-html.js @@ -72,881 +72,1115 @@ */ (function() { - - function trim(s) { - return s.replace(/^\s+|\s+$/g, ''); + function trim(s) { + return s.replace(/^\s+|\s+$/g, ''); + } + + function ltrim(s) { + return s.replace(/^\s+/g, ''); + } + + function rtrim(s) { + return s.replace(/\s+$/g, ''); + } + + function style_html(html_source, options, js_beautify, css_beautify) { + //Wrapper function to invoke all the necessary constructors and deal with the output. + + var multi_parser, + indent_inner_html, + indent_size, + indent_character, + wrap_line_length, + brace_style, + unformatted, + preserve_newlines, + max_preserve_newlines, + indent_handlebars, + wrap_attributes, + wrap_attributes_indent_size, + end_with_newline, + extra_liners, + eol; + + options = options || {}; + + // backwards compatibility to 1.3.4 + if ( + (options.wrap_line_length === undefined || + parseInt(options.wrap_line_length, 10) === 0) && + (options.max_char !== undefined && parseInt(options.max_char, 10) !== 0) + ) { + options.wrap_line_length = options.max_char; } - function ltrim(s) { - return s.replace(/^\s+/g, ''); + indent_inner_html = + options.indent_inner_html === undefined + ? false + : options.indent_inner_html; + indent_size = + options.indent_size === undefined ? 4 : parseInt(options.indent_size, 10); + indent_character = + options.indent_char === undefined ? ' ' : options.indent_char; + brace_style = + options.brace_style === undefined ? 'collapse' : options.brace_style; + wrap_line_length = + parseInt(options.wrap_line_length, 10) === 0 + ? 32786 + : parseInt(options.wrap_line_length || 250, 10); + unformatted = options.unformatted || [ + 'a', + 'span', + 'img', + 'bdo', + 'em', + 'strong', + 'dfn', + 'code', + 'samp', + 'kbd', + 'var', + 'cite', + 'abbr', + 'acronym', + 'q', + 'sub', + 'sup', + 'tt', + 'i', + 'b', + 'big', + 'small', + 'u', + 's', + 'strike', + 'font', + 'ins', + 'del', + 'pre', + 'address', + 'dt', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6' + ]; + preserve_newlines = + options.preserve_newlines === undefined + ? true + : options.preserve_newlines; + max_preserve_newlines = preserve_newlines + ? isNaN(parseInt(options.max_preserve_newlines, 10)) + ? 32786 + : parseInt(options.max_preserve_newlines, 10) + : 0; + indent_handlebars = + options.indent_handlebars === undefined + ? false + : options.indent_handlebars; + wrap_attributes = + options.wrap_attributes === undefined ? 'auto' : options.wrap_attributes; + wrap_attributes_indent_size = + options.wrap_attributes_indent_size === undefined + ? indent_size + : parseInt(options.wrap_attributes_indent_size, 10) || indent_size; + end_with_newline = + options.end_with_newline === undefined ? false : options.end_with_newline; + extra_liners = + typeof options.extra_liners == 'object' && options.extra_liners + ? options.extra_liners.concat() + : typeof options.extra_liners === 'string' + ? options.extra_liners.split(',') + : 'head,body,/html'.split(','); + eol = options.eol ? options.eol : '\n'; + + if (options.indent_with_tabs) { + indent_character = '\t'; + indent_size = 1; } - function rtrim(s) { - return s.replace(/\s+$/g,''); - } + eol = eol.replace(/\\r/, '\r').replace(/\\n/, '\n'); + + function Parser() { + this.pos = 0; //Parser position + this.token = ''; + this.current_mode = 'CONTENT'; //reflects the current Parser mode: TAG/CONTENT + this.tags = { + //An object to hold tags, their position, and their parent-tags, initiated with default values + parent: 'parent1', + parentcount: 1, + parent1: '' + }; + this.tag_type = ''; + this.token_text = this.last_token = this.last_text = this.token_type = ''; + this.newlines = 0; + this.indent_content = indent_inner_html; + + this.Utils = { + //Uilities made available to the various functions + whitespace: '\n\r\t '.split(''), + single_token: 'br,input,link,meta,source,!doctype,basefont,base,area,hr,wbr,param,img,isindex,embed'.split( + ',' + ), //all the single tags for HTML + extra_liners: extra_liners, //for tags that need a line of whitespace before them + in_array: function(what, arr) { + for (var i = 0; i < arr.length; i++) { + if (what === arr[i]) { + return true; + } + } + return false; + } + }; + + // Return true if the given text is composed entirely of whitespace. + this.is_whitespace = function(text) { + for (var n = 0; n < text.length; text++) { + if (!this.Utils.in_array(text.charAt(n), this.Utils.whitespace)) { + return false; + } + } + return true; + }; + + this.traverse_whitespace = function() { + var input_char = ''; + + input_char = this.input.charAt(this.pos); + if (this.Utils.in_array(input_char, this.Utils.whitespace)) { + this.newlines = 0; + while (this.Utils.in_array(input_char, this.Utils.whitespace)) { + if ( + preserve_newlines && + input_char === '\n' && + this.newlines <= max_preserve_newlines + ) { + this.newlines += 1; + } - function style_html(html_source, options, js_beautify, css_beautify) { - //Wrapper function to invoke all the necessary constructors and deal with the output. - - var multi_parser, - indent_inner_html, - indent_size, - indent_character, - wrap_line_length, - brace_style, - unformatted, - preserve_newlines, - max_preserve_newlines, - indent_handlebars, - wrap_attributes, - wrap_attributes_indent_size, - end_with_newline, - extra_liners, - eol; - - options = options || {}; - - // backwards compatibility to 1.3.4 - if ((options.wrap_line_length === undefined || parseInt(options.wrap_line_length, 10) === 0) && - (options.max_char !== undefined && parseInt(options.max_char, 10) !== 0)) { - options.wrap_line_length = options.max_char; + this.pos++; + input_char = this.input.charAt(this.pos); + } + return true; + } + return false; + }; + + // Append a space to the given content (string array) or, if we are + // at the wrap_line_length, append a newline/indentation. + this.space_or_wrap = function(content) { + if (this.line_char_count >= this.wrap_line_length) { + //insert a line when the wrap_line_length is reached + this.print_newline(false, content); + this.print_indentation(content); + } else { + this.line_char_count++; + content.push(' '); } + }; + + this.get_content = function() { + //function to capture regular content between tags + var input_char = '', + content = [], + space = false; //if a space is needed + + while (this.input.charAt(this.pos) !== '<') { + if (this.pos >= this.input.length) { + return content.length ? content.join('') : ['', 'TK_EOF']; + } + + if (this.traverse_whitespace()) { + this.space_or_wrap(content); + continue; + } + + if (indent_handlebars) { + // Handlebars parsing is complicated. + // {{#foo}} and {{/foo}} are formatted tags. + // {{something}} should get treated as content, except: + // {{else}} specifically behaves like {{#if}} and {{/if}} + var peek3 = this.input.substr(this.pos, 3); + if (peek3 === '{{#' || peek3 === '{{/') { + // These are tags and not content. + break; + } else if (peek3 === '{{!') { + return [this.get_tag(), 'TK_TAG_HANDLEBARS_COMMENT']; + } else if (this.input.substr(this.pos, 2) === '{{') { + if (this.get_tag(true) === '{{else}}') { + break; + } + } + } - indent_inner_html = (options.indent_inner_html === undefined) ? false : options.indent_inner_html; - indent_size = (options.indent_size === undefined) ? 4 : parseInt(options.indent_size, 10); - indent_character = (options.indent_char === undefined) ? ' ' : options.indent_char; - brace_style = (options.brace_style === undefined) ? 'collapse' : options.brace_style; - wrap_line_length = parseInt(options.wrap_line_length, 10) === 0 ? 32786 : parseInt(options.wrap_line_length || 250, 10); - unformatted = options.unformatted || ['a', 'span', 'img', 'bdo', 'em', 'strong', 'dfn', 'code', 'samp', 'kbd', - 'var', 'cite', 'abbr', 'acronym', 'q', 'sub', 'sup', 'tt', 'i', 'b', 'big', 'small', 'u', 's', 'strike', - 'font', 'ins', 'del', 'pre', 'address', 'dt', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6']; - preserve_newlines = (options.preserve_newlines === undefined) ? true : options.preserve_newlines; - max_preserve_newlines = preserve_newlines ? - (isNaN(parseInt(options.max_preserve_newlines, 10)) ? 32786 : parseInt(options.max_preserve_newlines, 10)) - : 0; - indent_handlebars = (options.indent_handlebars === undefined) ? false : options.indent_handlebars; - wrap_attributes = (options.wrap_attributes === undefined) ? 'auto' : options.wrap_attributes; - wrap_attributes_indent_size = (options.wrap_attributes_indent_size === undefined) ? indent_size : parseInt(options.wrap_attributes_indent_size, 10) || indent_size; - end_with_newline = (options.end_with_newline === undefined) ? false : options.end_with_newline; - extra_liners = (typeof options.extra_liners == 'object') && options.extra_liners ? - options.extra_liners.concat() : (typeof options.extra_liners === 'string') ? - options.extra_liners.split(',') : 'head,body,/html'.split(','); - eol = options.eol ? options.eol : '\n'; - - if(options.indent_with_tabs){ - indent_character = '\t'; - indent_size = 1; + input_char = this.input.charAt(this.pos); + this.pos++; + this.line_char_count++; + content.push(input_char); //letter at-a-time (or string) inserted to an array } + return content.length ? content.join('') : ''; + }; - eol = eol.replace(/\\r/, '\r').replace(/\\n/, '\n') - - function Parser() { - - this.pos = 0; //Parser position - this.token = ''; - this.current_mode = 'CONTENT'; //reflects the current Parser mode: TAG/CONTENT - this.tags = { //An object to hold tags, their position, and their parent-tags, initiated with default values - parent: 'parent1', - parentcount: 1, - parent1: '' - }; - this.tag_type = ''; - this.token_text = this.last_token = this.last_text = this.token_type = ''; - this.newlines = 0; - this.indent_content = indent_inner_html; - - this.Utils = { //Uilities made available to the various functions - whitespace: "\n\r\t ".split(''), - single_token: 'br,input,link,meta,source,!doctype,basefont,base,area,hr,wbr,param,img,isindex,embed'.split(','), //all the single tags for HTML - extra_liners: extra_liners, //for tags that need a line of whitespace before them - in_array: function(what, arr) { - for (var i = 0; i < arr.length; i++) { - if (what === arr[i]) { - return true; - } - } - return false; - } - }; - - // Return true if the given text is composed entirely of whitespace. - this.is_whitespace = function(text) { - for (var n = 0; n < text.length; text++) { - if (!this.Utils.in_array(text.charAt(n), this.Utils.whitespace)) { - return false; - } - } - return true; - }; - - this.traverse_whitespace = function() { - var input_char = ''; - - input_char = this.input.charAt(this.pos); - if (this.Utils.in_array(input_char, this.Utils.whitespace)) { - this.newlines = 0; - while (this.Utils.in_array(input_char, this.Utils.whitespace)) { - if (preserve_newlines && input_char === '\n' && this.newlines <= max_preserve_newlines) { - this.newlines += 1; - } - - this.pos++; - input_char = this.input.charAt(this.pos); - } - return true; - } - return false; - }; - - // Append a space to the given content (string array) or, if we are - // at the wrap_line_length, append a newline/indentation. - this.space_or_wrap = function(content) { - if (this.line_char_count >= this.wrap_line_length) { //insert a line when the wrap_line_length is reached - this.print_newline(false, content); - this.print_indentation(content); - } else { - this.line_char_count++; - content.push(' '); - } - }; - - this.get_content = function() { //function to capture regular content between tags - var input_char = '', - content = [], - space = false; //if a space is needed - - while (this.input.charAt(this.pos) !== '<') { - if (this.pos >= this.input.length) { - return content.length ? content.join('') : ['', 'TK_EOF']; - } - - if (this.traverse_whitespace()) { - this.space_or_wrap(content); - continue; - } - - if (indent_handlebars) { - // Handlebars parsing is complicated. - // {{#foo}} and {{/foo}} are formatted tags. - // {{something}} should get treated as content, except: - // {{else}} specifically behaves like {{#if}} and {{/if}} - var peek3 = this.input.substr(this.pos, 3); - if (peek3 === '{{#' || peek3 === '{{/') { - // These are tags and not content. - break; - } else if (peek3 === '{{!') { - return [this.get_tag(), 'TK_TAG_HANDLEBARS_COMMENT']; - } else if (this.input.substr(this.pos, 2) === '{{') { - if (this.get_tag(true) === '{{else}}') { - break; - } - } - } - - input_char = this.input.charAt(this.pos); - this.pos++; - this.line_char_count++; - content.push(input_char); //letter at-a-time (or string) inserted to an array - } - return content.length ? content.join('') : ''; - }; + this.get_contents_to = function(name) { + //get the full content of a script or style to pass to js_beautify + if (this.pos === this.input.length) { + return ['', 'TK_EOF']; + } + var input_char = ''; + var content = ''; + var reg_match = new RegExp('</' + name + '\\s*>', 'igm'); + reg_match.lastIndex = this.pos; + var reg_array = reg_match.exec(this.input); + var end_script = reg_array ? reg_array.index : this.input.length; //absolute end of script + if (this.pos < end_script) { + //get everything in between the script tags + content = this.input.substring(this.pos, end_script); + this.pos = end_script; + } + return content; + }; + + this.record_tag = function(tag) { + //function to record a tag and its parent in this.tags Object + if (this.tags[tag + 'count']) { + //check for the existence of this tag type + this.tags[tag + 'count']++; + this.tags[tag + this.tags[tag + 'count']] = this.indent_level; //and record the present indent level + } else { + //otherwise initialize this tag type + this.tags[tag + 'count'] = 1; + this.tags[tag + this.tags[tag + 'count']] = this.indent_level; //and record the present indent level + } + this.tags[tag + this.tags[tag + 'count'] + 'parent'] = this.tags.parent; //set the parent (i.e. in the case of a div this.tags.div1parent) + this.tags.parent = tag + this.tags[tag + 'count']; //and make this the current parent (i.e. in the case of a div 'div1') + }; + + this.retrieve_tag = function(tag) { + //function to retrieve the opening tag to the corresponding closer + if (this.tags[tag + 'count']) { + //if the openener is not in the Object we ignore it + var temp_parent = this.tags.parent; //check to see if it's a closable tag. + while (temp_parent) { + //till we reach '' (the initial value); + if (tag + this.tags[tag + 'count'] === temp_parent) { + //if this is it use it + break; + } + temp_parent = this.tags[temp_parent + 'parent']; //otherwise keep on climbing up the DOM Tree + } + if (temp_parent) { + //if we caught something + this.indent_level = this.tags[tag + this.tags[tag + 'count']]; //set the indent_level accordingly + this.tags.parent = this.tags[temp_parent + 'parent']; //and set the current parent + } + delete this.tags[tag + this.tags[tag + 'count'] + 'parent']; //delete the closed tags parent reference... + delete this.tags[tag + this.tags[tag + 'count']]; //...and the tag itself + if (this.tags[tag + 'count'] === 1) { + delete this.tags[tag + 'count']; + } else { + this.tags[tag + 'count']--; + } + } + }; - this.get_contents_to = function(name) { //get the full content of a script or style to pass to js_beautify - if (this.pos === this.input.length) { - return ['', 'TK_EOF']; - } - var input_char = ''; - var content = ''; - var reg_match = new RegExp('</' + name + '\\s*>', 'igm'); - reg_match.lastIndex = this.pos; - var reg_array = reg_match.exec(this.input); - var end_script = reg_array ? reg_array.index : this.input.length; //absolute end of script - if (this.pos < end_script) { //get everything in between the script tags - content = this.input.substring(this.pos, end_script); - this.pos = end_script; - } - return content; - }; - - this.record_tag = function(tag) { //function to record a tag and its parent in this.tags Object - if (this.tags[tag + 'count']) { //check for the existence of this tag type - this.tags[tag + 'count']++; - this.tags[tag + this.tags[tag + 'count']] = this.indent_level; //and record the present indent level - } else { //otherwise initialize this tag type - this.tags[tag + 'count'] = 1; - this.tags[tag + this.tags[tag + 'count']] = this.indent_level; //and record the present indent level - } - this.tags[tag + this.tags[tag + 'count'] + 'parent'] = this.tags.parent; //set the parent (i.e. in the case of a div this.tags.div1parent) - this.tags.parent = tag + this.tags[tag + 'count']; //and make this the current parent (i.e. in the case of a div 'div1') - }; - - this.retrieve_tag = function(tag) { //function to retrieve the opening tag to the corresponding closer - if (this.tags[tag + 'count']) { //if the openener is not in the Object we ignore it - var temp_parent = this.tags.parent; //check to see if it's a closable tag. - while (temp_parent) { //till we reach '' (the initial value); - if (tag + this.tags[tag + 'count'] === temp_parent) { //if this is it use it - break; - } - temp_parent = this.tags[temp_parent + 'parent']; //otherwise keep on climbing up the DOM Tree - } - if (temp_parent) { //if we caught something - this.indent_level = this.tags[tag + this.tags[tag + 'count']]; //set the indent_level accordingly - this.tags.parent = this.tags[temp_parent + 'parent']; //and set the current parent - } - delete this.tags[tag + this.tags[tag + 'count'] + 'parent']; //delete the closed tags parent reference... - delete this.tags[tag + this.tags[tag + 'count']]; //...and the tag itself - if (this.tags[tag + 'count'] === 1) { - delete this.tags[tag + 'count']; - } else { - this.tags[tag + 'count']--; - } - } - }; + this.indent_to_tag = function(tag) { + // Match the indentation level to the last use of this tag, but don't remove it. + if (!this.tags[tag + 'count']) { + return; + } + var temp_parent = this.tags.parent; + while (temp_parent) { + if (tag + this.tags[tag + 'count'] === temp_parent) { + break; + } + temp_parent = this.tags[temp_parent + 'parent']; + } + if (temp_parent) { + this.indent_level = this.tags[tag + this.tags[tag + 'count']]; + } + }; + + this.get_tag = function(peek) { + //function to get a full tag and parse its type + var input_char = '', + content = [], + comment = '', + space = false, + first_attr = true, + tag_start, + tag_end, + tag_start_char, + orig_pos = this.pos, + orig_line_char_count = this.line_char_count; + + peek = peek !== undefined ? peek : false; + + do { + if (this.pos >= this.input.length) { + if (peek) { + this.pos = orig_pos; + this.line_char_count = orig_line_char_count; + } + return content.length ? content.join('') : ['', 'TK_EOF']; + } + + input_char = this.input.charAt(this.pos); + this.pos++; + + if (this.Utils.in_array(input_char, this.Utils.whitespace)) { + //don't want to insert unnecessary space + space = true; + continue; + } + + if (input_char === "'" || input_char === '"') { + input_char += this.get_unformatted(input_char); + space = true; + } + + if (input_char === '=') { + //no space before = + space = false; + } + + if ( + content.length && + content[content.length - 1] !== '=' && + input_char !== '>' && + space + ) { + //no space after = or before > + this.space_or_wrap(content); + space = false; + if ( + !first_attr && + wrap_attributes === 'force' && + input_char !== '/' + ) { + this.print_newline(true, content); + this.print_indentation(content); + for ( + var count = 0; + count < wrap_attributes_indent_size; + count++ + ) { + content.push(indent_character); + } + } + for (var i = 0; i < content.length; i++) { + if (content[i] === ' ') { + first_attr = false; + break; + } + } + } + + if (indent_handlebars && tag_start_char === '<') { + // When inside an angle-bracket tag, put spaces around + // handlebars not inside of strings. + if (input_char + this.input.charAt(this.pos) === '{{') { + input_char += this.get_unformatted('}}'); + if ( + content.length && + content[content.length - 1] !== ' ' && + content[content.length - 1] !== '<' + ) { + input_char = ' ' + input_char; + } + space = true; + } + } + + if (input_char === '<' && !tag_start_char) { + tag_start = this.pos - 1; + tag_start_char = '<'; + } + + if (indent_handlebars && !tag_start_char) { + if ( + content.length >= 2 && + content[content.length - 1] === '{' && + content[content.length - 2] === '{' + ) { + if ( + input_char === '#' || + input_char === '/' || + input_char === '!' + ) { + tag_start = this.pos - 3; + } else { + tag_start = this.pos - 2; + } + tag_start_char = '{'; + } + } + + this.line_char_count++; + content.push(input_char); //inserts character at-a-time (or string) + + if ( + content[1] && + (content[1] === '!' || content[1] === '?' || content[1] === '%') + ) { + //if we're in a comment, do something special + // We treat all comments as literals, even more than preformatted tags + // we just look for the appropriate close tag + content = [this.get_comment(tag_start)]; + break; + } + + if ( + indent_handlebars && + content[1] && + content[1] === '{' && + content[2] && + content[2] === '!' + ) { + //if we're in a comment, do something special + // We treat all comments as literals, even more than preformatted tags + // we just look for the appropriate close tag + content = [this.get_comment(tag_start)]; + break; + } + + if ( + indent_handlebars && + tag_start_char === '{' && + content.length > 2 && + content[content.length - 2] === '}' && + content[content.length - 1] === '}' + ) { + break; + } + } while (input_char !== '>'); + + var tag_complete = content.join(''); + var tag_index; + var tag_offset; + + if (tag_complete.indexOf(' ') !== -1) { + //if there's whitespace, thats where the tag name ends + tag_index = tag_complete.indexOf(' '); + } else if (tag_complete.charAt(0) === '{') { + tag_index = tag_complete.indexOf('}'); + } else { + //otherwise go with the tag ending + tag_index = tag_complete.indexOf('>'); + } + if (tag_complete.charAt(0) === '<' || !indent_handlebars) { + tag_offset = 1; + } else { + tag_offset = tag_complete.charAt(2) === '#' ? 3 : 2; + } + var tag_check = tag_complete + .substring(tag_offset, tag_index) + .toLowerCase(); + if ( + tag_complete.charAt(tag_complete.length - 2) === '/' || + this.Utils.in_array(tag_check, this.Utils.single_token) + ) { + //if this tag name is a single tag type (either in the list or has a closing /) + if (!peek) { + this.tag_type = 'SINGLE'; + } + } else if ( + indent_handlebars && + tag_complete.charAt(0) === '{' && + tag_check === 'else' + ) { + if (!peek) { + this.indent_to_tag('if'); + this.tag_type = 'HANDLEBARS_ELSE'; + this.indent_content = true; + this.traverse_whitespace(); + } + } else if (this.is_unformatted(tag_check, unformatted)) { + // do not reformat the "unformatted" tags + comment = this.get_unformatted('</' + tag_check + '>', tag_complete); //...delegate to get_unformatted function + content.push(comment); + tag_end = this.pos - 1; + this.tag_type = 'SINGLE'; + } else if ( + tag_check === 'script' && + (tag_complete.search('type') === -1 || + (tag_complete.search('type') > -1 && + tag_complete.search( + /\b(text|application)\/(x-)?(javascript|ecmascript|jscript|livescript)/ + ) > -1)) + ) { + if (!peek) { + this.record_tag(tag_check); + this.tag_type = 'SCRIPT'; + } + } else if ( + tag_check === 'style' && + (tag_complete.search('type') === -1 || + (tag_complete.search('type') > -1 && + tag_complete.search('text/css') > -1)) + ) { + if (!peek) { + this.record_tag(tag_check); + this.tag_type = 'STYLE'; + } + } else if (tag_check.charAt(0) === '!') { + //peek for <! comment + // for comments content is already correct. + if (!peek) { + this.tag_type = 'SINGLE'; + this.traverse_whitespace(); + } + } else if (!peek) { + if (tag_check.charAt(0) === '/') { + //this tag is a double tag so check for tag-ending + this.retrieve_tag(tag_check.substring(1)); //remove it and all ancestors + this.tag_type = 'END'; + } else { + //otherwise it's a start-tag + this.record_tag(tag_check); //push it on the tag stack + if (tag_check.toLowerCase() !== 'html') { + this.indent_content = true; + } + this.tag_type = 'START'; + } + + // Allow preserving of newlines after a start or end tag + if (this.traverse_whitespace()) { + this.space_or_wrap(content); + } + + if (this.Utils.in_array(tag_check, this.Utils.extra_liners)) { + //check if this double needs an extra line + this.print_newline(false, this.output); + if ( + this.output.length && + this.output[this.output.length - 2] !== '\n' + ) { + this.print_newline(true, this.output); + } + } + } - this.indent_to_tag = function(tag) { - // Match the indentation level to the last use of this tag, but don't remove it. - if (!this.tags[tag + 'count']) { - return; - } - var temp_parent = this.tags.parent; - while (temp_parent) { - if (tag + this.tags[tag + 'count'] === temp_parent) { - break; - } - temp_parent = this.tags[temp_parent + 'parent']; - } - if (temp_parent) { - this.indent_level = this.tags[tag + this.tags[tag + 'count']]; - } - }; - - this.get_tag = function(peek) { //function to get a full tag and parse its type - var input_char = '', - content = [], - comment = '', - space = false, - first_attr = true, - tag_start, tag_end, - tag_start_char, - orig_pos = this.pos, - orig_line_char_count = this.line_char_count; - - peek = peek !== undefined ? peek : false; - - do { - if (this.pos >= this.input.length) { - if (peek) { - this.pos = orig_pos; - this.line_char_count = orig_line_char_count; - } - return content.length ? content.join('') : ['', 'TK_EOF']; - } - - input_char = this.input.charAt(this.pos); - this.pos++; - - if (this.Utils.in_array(input_char, this.Utils.whitespace)) { //don't want to insert unnecessary space - space = true; - continue; - } - - if (input_char === "'" || input_char === '"') { - input_char += this.get_unformatted(input_char); - space = true; - - } - - if (input_char === '=') { //no space before = - space = false; - } - - if (content.length && content[content.length - 1] !== '=' && input_char !== '>' && space) { - //no space after = or before > - this.space_or_wrap(content); - space = false; - if (!first_attr && wrap_attributes === 'force' && input_char !== '/') { - this.print_newline(true, content); - this.print_indentation(content); - for (var count = 0; count < wrap_attributes_indent_size; count++) { - content.push(indent_character); - } - } - for (var i = 0; i < content.length; i++) { - if (content[i] === ' ') { - first_attr = false; - break; - } - } - } - - if (indent_handlebars && tag_start_char === '<') { - // When inside an angle-bracket tag, put spaces around - // handlebars not inside of strings. - if ((input_char + this.input.charAt(this.pos)) === '{{') { - input_char += this.get_unformatted('}}'); - if (content.length && content[content.length - 1] !== ' ' && content[content.length - 1] !== '<') { - input_char = ' ' + input_char; - } - space = true; - } - } - - if (input_char === '<' && !tag_start_char) { - tag_start = this.pos - 1; - tag_start_char = '<'; - } - - if (indent_handlebars && !tag_start_char) { - if (content.length >= 2 && content[content.length - 1] === '{' && content[content.length - 2] === '{') { - if (input_char === '#' || input_char === '/' || input_char === '!') { - tag_start = this.pos - 3; - } else { - tag_start = this.pos - 2; - } - tag_start_char = '{'; - } - } - - this.line_char_count++; - content.push(input_char); //inserts character at-a-time (or string) - - if (content[1] && (content[1] === '!' || content[1] === '?' || content[1] === '%')) { //if we're in a comment, do something special - // We treat all comments as literals, even more than preformatted tags - // we just look for the appropriate close tag - content = [this.get_comment(tag_start)]; - break; - } - - if (indent_handlebars && content[1] && content[1] === '{' && content[2] && content[2] === '!') { //if we're in a comment, do something special - // We treat all comments as literals, even more than preformatted tags - // we just look for the appropriate close tag - content = [this.get_comment(tag_start)]; - break; - } - - if (indent_handlebars && tag_start_char === '{' && content.length > 2 && content[content.length - 2] === '}' && content[content.length - 1] === '}') { - break; - } - } while (input_char !== '>'); - - var tag_complete = content.join(''); - var tag_index; - var tag_offset; - - if (tag_complete.indexOf(' ') !== -1) { //if there's whitespace, thats where the tag name ends - tag_index = tag_complete.indexOf(' '); - } else if (tag_complete.charAt(0) === '{') { - tag_index = tag_complete.indexOf('}'); - } else { //otherwise go with the tag ending - tag_index = tag_complete.indexOf('>'); - } - if (tag_complete.charAt(0) === '<' || !indent_handlebars) { - tag_offset = 1; - } else { - tag_offset = tag_complete.charAt(2) === '#' ? 3 : 2; - } - var tag_check = tag_complete.substring(tag_offset, tag_index).toLowerCase(); - if (tag_complete.charAt(tag_complete.length - 2) === '/' || - this.Utils.in_array(tag_check, this.Utils.single_token)) { //if this tag name is a single tag type (either in the list or has a closing /) - if (!peek) { - this.tag_type = 'SINGLE'; - } - } else if (indent_handlebars && tag_complete.charAt(0) === '{' && tag_check === 'else') { - if (!peek) { - this.indent_to_tag('if'); - this.tag_type = 'HANDLEBARS_ELSE'; - this.indent_content = true; - this.traverse_whitespace(); - } - } else if (this.is_unformatted(tag_check, unformatted)) { // do not reformat the "unformatted" tags - comment = this.get_unformatted('</' + tag_check + '>', tag_complete); //...delegate to get_unformatted function - content.push(comment); - tag_end = this.pos - 1; - this.tag_type = 'SINGLE'; - } else if (tag_check === 'script' && - (tag_complete.search('type') === -1 || - (tag_complete.search('type') > -1 && - tag_complete.search(/\b(text|application)\/(x-)?(javascript|ecmascript|jscript|livescript)/) > -1))) { - if (!peek) { - this.record_tag(tag_check); - this.tag_type = 'SCRIPT'; - } - } else if (tag_check === 'style' && - (tag_complete.search('type') === -1 || - (tag_complete.search('type') > -1 && tag_complete.search('text/css') > -1))) { - if (!peek) { - this.record_tag(tag_check); - this.tag_type = 'STYLE'; - } - } else if (tag_check.charAt(0) === '!') { //peek for <! comment - // for comments content is already correct. - if (!peek) { - this.tag_type = 'SINGLE'; - this.traverse_whitespace(); - } - } else if (!peek) { - if (tag_check.charAt(0) === '/') { //this tag is a double tag so check for tag-ending - this.retrieve_tag(tag_check.substring(1)); //remove it and all ancestors - this.tag_type = 'END'; - } else { //otherwise it's a start-tag - this.record_tag(tag_check); //push it on the tag stack - if (tag_check.toLowerCase() !== 'html') { - this.indent_content = true; - } - this.tag_type = 'START'; - } - - // Allow preserving of newlines after a start or end tag - if (this.traverse_whitespace()) { - this.space_or_wrap(content); - } - - if (this.Utils.in_array(tag_check, this.Utils.extra_liners)) { //check if this double needs an extra line - this.print_newline(false, this.output); - if (this.output.length && this.output[this.output.length - 2] !== '\n') { - this.print_newline(true, this.output); - } - } - } + if (peek) { + this.pos = orig_pos; + this.line_char_count = orig_line_char_count; + } - if (peek) { - this.pos = orig_pos; - this.line_char_count = orig_line_char_count; - } + return content.join(''); //returns fully formatted tag + }; + + this.get_comment = function(start_pos) { + //function to return comment content in its entirety + // this is will have very poor perf, but will work for now. + var comment = '', + delimiter = '>', + matched = false; + + this.pos = start_pos; + input_char = this.input.charAt(this.pos); + this.pos++; + + while (this.pos <= this.input.length) { + comment += input_char; + + // only need to check for the delimiter if the last chars match + if ( + comment.charAt(comment.length - 1) === + delimiter.charAt(delimiter.length - 1) && + comment.indexOf(delimiter) !== -1 + ) { + break; + } + + // only need to search for custom delimiter for the first few characters + if (!matched && comment.length < 10) { + if (comment.indexOf('<![if') === 0) { + //peek for <![if conditional comment + delimiter = '<![endif]>'; + matched = true; + } else if (comment.indexOf('<![cdata[') === 0) { + //if it's a <[cdata[ comment... + delimiter = ']]>'; + matched = true; + } else if (comment.indexOf('<![') === 0) { + // some other ![ comment? ... + delimiter = ']>'; + matched = true; + } else if (comment.indexOf('<!--') === 0) { + // <!-- comment ... + delimiter = '-->'; + matched = true; + } else if (comment.indexOf('{{!') === 0) { + // {{! handlebars comment + delimiter = '}}'; + matched = true; + } else if (comment.indexOf('<?') === 0) { + // {{! handlebars comment + delimiter = '?>'; + matched = true; + } else if (comment.indexOf('<%') === 0) { + // {{! handlebars comment + delimiter = '%>'; + matched = true; + } + } - return content.join(''); //returns fully formatted tag - }; - - this.get_comment = function(start_pos) { //function to return comment content in its entirety - // this is will have very poor perf, but will work for now. - var comment = '', - delimiter = '>', - matched = false; - - this.pos = start_pos; - input_char = this.input.charAt(this.pos); - this.pos++; - - while (this.pos <= this.input.length) { - comment += input_char; - - // only need to check for the delimiter if the last chars match - if (comment.charAt(comment.length - 1) === delimiter.charAt(delimiter.length - 1) && - comment.indexOf(delimiter) !== -1) { - break; - } - - // only need to search for custom delimiter for the first few characters - if (!matched && comment.length < 10) { - if (comment.indexOf('<![if') === 0) { //peek for <![if conditional comment - delimiter = '<![endif]>'; - matched = true; - } else if (comment.indexOf('<![cdata[') === 0) { //if it's a <[cdata[ comment... - delimiter = ']]>'; - matched = true; - } else if (comment.indexOf('<![') === 0) { // some other ![ comment? ... - delimiter = ']>'; - matched = true; - } else if (comment.indexOf('<!--') === 0) { // <!-- comment ... - delimiter = '-->'; - matched = true; - } else if (comment.indexOf('{{!') === 0) { // {{! handlebars comment - delimiter = '}}'; - matched = true; - } else if (comment.indexOf('<?') === 0) { // {{! handlebars comment - delimiter = '?>'; - matched = true; - } else if (comment.indexOf('<%') === 0) { // {{! handlebars comment - delimiter = '%>'; - matched = true; - } - } - - input_char = this.input.charAt(this.pos); - this.pos++; - } + input_char = this.input.charAt(this.pos); + this.pos++; + } - return comment; - }; + return comment; + }; - this.get_unformatted = function(delimiter, orig_tag) { //function to return unformatted content in its entirety + this.get_unformatted = function(delimiter, orig_tag) { + //function to return unformatted content in its entirety - if (orig_tag && orig_tag.toLowerCase().indexOf(delimiter) !== -1) { - return ''; - } - var input_char = ''; - var content = ''; - var min_index = 0; - var space = true; - do { - - if (this.pos >= this.input.length) { - return content; - } - - input_char = this.input.charAt(this.pos); - this.pos++; - - if (this.Utils.in_array(input_char, this.Utils.whitespace)) { - if (!space) { - this.line_char_count--; - continue; - } - if (input_char === '\n' || input_char === '\r') { - content += '\n'; - /* Don't change tab indention for unformatted blocks. If using code for html editing, this will greatly affect <pre> tags if they are specified in the 'unformatted array' + if (orig_tag && orig_tag.toLowerCase().indexOf(delimiter) !== -1) { + return ''; + } + var input_char = ''; + var content = ''; + var min_index = 0; + var space = true; + do { + if (this.pos >= this.input.length) { + return content; + } + + input_char = this.input.charAt(this.pos); + this.pos++; + + if (this.Utils.in_array(input_char, this.Utils.whitespace)) { + if (!space) { + this.line_char_count--; + continue; + } + if (input_char === '\n' || input_char === '\r') { + content += '\n'; + /* Don't change tab indention for unformatted blocks. If using code for html editing, this will greatly affect <pre> tags if they are specified in the 'unformatted array' for (var i=0; i<this.indent_level; i++) { content += this.indent_string; } space = false; //...and make sure other indentation is erased */ - this.line_char_count = 0; - continue; - } - } - content += input_char; - this.line_char_count++; - space = true; - - if (indent_handlebars && input_char === '{' && content.length && content.charAt(content.length - 2) === '{') { - // Handlebars expressions in strings should also be unformatted. - content += this.get_unformatted('}}'); - // These expressions are opaque. Ignore delimiters found in them. - min_index = content.length; - } - } while (content.toLowerCase().indexOf(delimiter, min_index) === -1); - return content; - }; - - this.get_token = function() { //initial handler for token-retrieval - var token; - - if (this.last_token === 'TK_TAG_SCRIPT' || this.last_token === 'TK_TAG_STYLE') { //check if we need to format javascript - var type = this.last_token.substr(7); - token = this.get_contents_to(type); - if (typeof token !== 'string') { - return token; - } - return [token, 'TK_' + type]; - } - if (this.current_mode === 'CONTENT') { - token = this.get_content(); - if (typeof token !== 'string') { - return token; - } else { - return [token, 'TK_CONTENT']; - } - } - - if (this.current_mode === 'TAG') { - token = this.get_tag(); - if (typeof token !== 'string') { - return token; - } else { - var tag_name_type = 'TK_TAG_' + this.tag_type; - return [token, tag_name_type]; - } - } - }; - - this.get_full_indent = function(level) { - level = this.indent_level + level || 0; - if (level < 1) { - return ''; - } + this.line_char_count = 0; + continue; + } + } + content += input_char; + this.line_char_count++; + space = true; + + if ( + indent_handlebars && + input_char === '{' && + content.length && + content.charAt(content.length - 2) === '{' + ) { + // Handlebars expressions in strings should also be unformatted. + content += this.get_unformatted('}}'); + // These expressions are opaque. Ignore delimiters found in them. + min_index = content.length; + } + } while (content.toLowerCase().indexOf(delimiter, min_index) === -1); + return content; + }; + + this.get_token = function() { + //initial handler for token-retrieval + var token; + + if ( + this.last_token === 'TK_TAG_SCRIPT' || + this.last_token === 'TK_TAG_STYLE' + ) { + //check if we need to format javascript + var type = this.last_token.substr(7); + token = this.get_contents_to(type); + if (typeof token !== 'string') { + return token; + } + return [token, 'TK_' + type]; + } + if (this.current_mode === 'CONTENT') { + token = this.get_content(); + if (typeof token !== 'string') { + return token; + } else { + return [token, 'TK_CONTENT']; + } + } - return Array(level + 1).join(this.indent_string); - }; + if (this.current_mode === 'TAG') { + token = this.get_tag(); + if (typeof token !== 'string') { + return token; + } else { + var tag_name_type = 'TK_TAG_' + this.tag_type; + return [token, tag_name_type]; + } + } + }; - this.is_unformatted = function(tag_check, unformatted) { - //is this an HTML5 block-level link? - if (!this.Utils.in_array(tag_check, unformatted)) { - return false; - } + this.get_full_indent = function(level) { + level = this.indent_level + level || 0; + if (level < 1) { + return ''; + } - if (tag_check.toLowerCase() !== 'a' || !this.Utils.in_array('a', unformatted)) { - return true; - } + return Array(level + 1).join(this.indent_string); + }; - //at this point we have an tag; is its first child something we want to remain - //unformatted? - var next_tag = this.get_tag(true /* peek. */ ); + this.is_unformatted = function(tag_check, unformatted) { + //is this an HTML5 block-level link? + if (!this.Utils.in_array(tag_check, unformatted)) { + return false; + } - // test next_tag to see if it is just html tag (no external content) - var tag = (next_tag || "").match(/^\s*<\s*\/?([a-z]*)\s*[^>]*>\s*$/); + if ( + tag_check.toLowerCase() !== 'a' || + !this.Utils.in_array('a', unformatted) + ) { + return true; + } - // if next_tag comes back but is not an isolated tag, then - // let's treat the 'a' tag as having content - // and respect the unformatted option - if (!tag || this.Utils.in_array(tag, unformatted)) { - return true; - } else { - return false; - } - }; + //at this point we have an tag; is its first child something we want to remain + //unformatted? + var next_tag = this.get_tag(true /* peek. */); - this.printer = function(js_source, indent_character, indent_size, wrap_line_length, brace_style) { //handles input/output and some other printing functions + // test next_tag to see if it is just html tag (no external content) + var tag = (next_tag || '').match(/^\s*<\s*\/?([a-z]*)\s*[^>]*>\s*$/); - this.input = js_source || ''; //gets the input for the Parser + // if next_tag comes back but is not an isolated tag, then + // let's treat the 'a' tag as having content + // and respect the unformatted option + if (!tag || this.Utils.in_array(tag, unformatted)) { + return true; + } else { + return false; + } + }; + + this.printer = function( + js_source, + indent_character, + indent_size, + wrap_line_length, + brace_style + ) { + //handles input/output and some other printing functions + + this.input = js_source || ''; //gets the input for the Parser + + // HACK: newline parsing inconsistent. This brute force normalizes the input. + this.input = this.input.replace(/\r\n|[\r\u2028\u2029]/g, '\n'); + + this.output = []; + this.indent_character = indent_character; + this.indent_string = ''; + this.indent_size = indent_size; + this.brace_style = brace_style; + this.indent_level = 0; + this.wrap_line_length = wrap_line_length; + this.line_char_count = 0; //count to see if wrap_line_length was exceeded + + for (var i = 0; i < this.indent_size; i++) { + this.indent_string += this.indent_character; + } - // HACK: newline parsing inconsistent. This brute force normalizes the input. - this.input = this.input.replace(/\r\n|[\r\u2028\u2029]/g, '\n') + this.print_newline = function(force, arr) { + this.line_char_count = 0; + if (!arr || !arr.length) { + return; + } + if (force || arr[arr.length - 1] !== '\n') { + //we might want the extra line + if (arr[arr.length - 1] !== '\n') { + arr[arr.length - 1] = rtrim(arr[arr.length - 1]); + } + arr.push('\n'); + } + }; - this.output = []; - this.indent_character = indent_character; - this.indent_string = ''; - this.indent_size = indent_size; - this.brace_style = brace_style; - this.indent_level = 0; - this.wrap_line_length = wrap_line_length; - this.line_char_count = 0; //count to see if wrap_line_length was exceeded + this.print_indentation = function(arr) { + for (var i = 0; i < this.indent_level; i++) { + arr.push(this.indent_string); + this.line_char_count += this.indent_string.length; + } + }; - for (var i = 0; i < this.indent_size; i++) { - this.indent_string += this.indent_character; - } + this.print_token = function(text) { + // Avoid printing initial whitespace. + if (this.is_whitespace(text) && !this.output.length) { + return; + } + if (text || text !== '') { + if ( + this.output.length && + this.output[this.output.length - 1] === '\n' + ) { + this.print_indentation(this.output); + text = ltrim(text); + } + } + this.print_token_raw(text); + }; - this.print_newline = function(force, arr) { - this.line_char_count = 0; - if (!arr || !arr.length) { - return; - } - if (force || (arr[arr.length - 1] !== '\n')) { //we might want the extra line - if ((arr[arr.length - 1] !== '\n')) { - arr[arr.length - 1] = rtrim(arr[arr.length - 1]); - } - arr.push('\n'); - } - }; - - this.print_indentation = function(arr) { - for (var i = 0; i < this.indent_level; i++) { - arr.push(this.indent_string); - this.line_char_count += this.indent_string.length; - } - }; - - this.print_token = function(text) { - // Avoid printing initial whitespace. - if (this.is_whitespace(text) && !this.output.length) { - return; - } - if (text || text !== '') { - if (this.output.length && this.output[this.output.length - 1] === '\n') { - this.print_indentation(this.output); - text = ltrim(text); - } - } - this.print_token_raw(text); - }; - - this.print_token_raw = function(text) { - // If we are going to print newlines, truncate trailing - // whitespace, as the newlines will represent the space. - if (this.newlines > 0) { - text = rtrim(text); - } - - if (text && text !== '') { - if (text.length > 1 && text.charAt(text.length - 1) === '\n') { - // unformatted tags can grab newlines as their last character - this.output.push(text.slice(0, -1)); - this.print_newline(false, this.output); - } else { - this.output.push(text); - } - } - - for (var n = 0; n < this.newlines; n++) { - this.print_newline(n > 0, this.output); - } - this.newlines = 0; - }; - - this.indent = function() { - this.indent_level++; - }; - - this.unindent = function() { - if (this.indent_level > 0) { - this.indent_level--; - } - }; - }; - return this; - } + this.print_token_raw = function(text) { + // If we are going to print newlines, truncate trailing + // whitespace, as the newlines will represent the space. + if (this.newlines > 0) { + text = rtrim(text); + } + + if (text && text !== '') { + if (text.length > 1 && text.charAt(text.length - 1) === '\n') { + // unformatted tags can grab newlines as their last character + this.output.push(text.slice(0, -1)); + this.print_newline(false, this.output); + } else { + this.output.push(text); + } + } - /*_____________________--------------------_____________________*/ + for (var n = 0; n < this.newlines; n++) { + this.print_newline(n > 0, this.output); + } + this.newlines = 0; + }; - multi_parser = new Parser(); //wrapping functions Parser - multi_parser.printer(html_source, indent_character, indent_size, wrap_line_length, brace_style); //initialize starting values + this.indent = function() { + this.indent_level++; + }; - while (true) { - var t = multi_parser.get_token(); - multi_parser.token_text = t[0]; - multi_parser.token_type = t[1]; + this.unindent = function() { + if (this.indent_level > 0) { + this.indent_level--; + } + }; + }; + return this; + } - if (multi_parser.token_type === 'TK_EOF') { - break; + /*_____________________--------------------_____________________*/ + + multi_parser = new Parser(); //wrapping functions Parser + multi_parser.printer( + html_source, + indent_character, + indent_size, + wrap_line_length, + brace_style + ); //initialize starting values + + while (true) { + var t = multi_parser.get_token(); + multi_parser.token_text = t[0]; + multi_parser.token_type = t[1]; + + if (multi_parser.token_type === 'TK_EOF') { + break; + } + + switch (multi_parser.token_type) { + case 'TK_TAG_START': + multi_parser.print_newline(false, multi_parser.output); + multi_parser.print_token(multi_parser.token_text); + if (multi_parser.indent_content) { + multi_parser.indent(); + multi_parser.indent_content = false; + } + multi_parser.current_mode = 'CONTENT'; + break; + case 'TK_TAG_STYLE': + case 'TK_TAG_SCRIPT': + multi_parser.print_newline(false, multi_parser.output); + multi_parser.print_token(multi_parser.token_text); + multi_parser.current_mode = 'CONTENT'; + break; + case 'TK_TAG_END': + //Print new line only if the tag has no content and has child + if ( + multi_parser.last_token === 'TK_CONTENT' && + multi_parser.last_text === '' + ) { + var tag_name = multi_parser.token_text.match(/\w+/)[0]; + var tag_extracted_from_last_output = null; + if (multi_parser.output.length) { + tag_extracted_from_last_output = multi_parser.output[ + multi_parser.output.length - 1 + ].match(/(?:<|{{#)\s*(\w+)/); } - - switch (multi_parser.token_type) { - case 'TK_TAG_START': - multi_parser.print_newline(false, multi_parser.output); - multi_parser.print_token(multi_parser.token_text); - if (multi_parser.indent_content) { - multi_parser.indent(); - multi_parser.indent_content = false; - } - multi_parser.current_mode = 'CONTENT'; - break; - case 'TK_TAG_STYLE': - case 'TK_TAG_SCRIPT': - multi_parser.print_newline(false, multi_parser.output); - multi_parser.print_token(multi_parser.token_text); - multi_parser.current_mode = 'CONTENT'; - break; - case 'TK_TAG_END': - //Print new line only if the tag has no content and has child - if (multi_parser.last_token === 'TK_CONTENT' && multi_parser.last_text === '') { - var tag_name = multi_parser.token_text.match(/\w+/)[0]; - var tag_extracted_from_last_output = null; - if (multi_parser.output.length) { - tag_extracted_from_last_output = multi_parser.output[multi_parser.output.length - 1].match(/(?:<|{{#)\s*(\w+)/); - } - if (tag_extracted_from_last_output === null || - (tag_extracted_from_last_output[1] !== tag_name && !multi_parser.Utils.in_array(tag_extracted_from_last_output[1], unformatted))) { - multi_parser.print_newline(false, multi_parser.output); - } - } - multi_parser.print_token(multi_parser.token_text); - multi_parser.current_mode = 'CONTENT'; - break; - case 'TK_TAG_SINGLE': - // Don't add a newline before elements that should remain unformatted. - var tag_check = multi_parser.token_text.match(/^\s*<([a-z-]+)/i); - if (!tag_check || !multi_parser.Utils.in_array(tag_check[1], unformatted)) { - multi_parser.print_newline(false, multi_parser.output); - } - multi_parser.print_token(multi_parser.token_text); - multi_parser.current_mode = 'CONTENT'; - break; - case 'TK_TAG_HANDLEBARS_ELSE': - multi_parser.print_token(multi_parser.token_text); - if (multi_parser.indent_content) { - multi_parser.indent(); - multi_parser.indent_content = false; - } - multi_parser.current_mode = 'CONTENT'; - break; - case 'TK_TAG_HANDLEBARS_COMMENT': - multi_parser.print_token(multi_parser.token_text); - multi_parser.current_mode = 'TAG'; - break; - case 'TK_CONTENT': - multi_parser.print_token(multi_parser.token_text); - multi_parser.current_mode = 'TAG'; - break; - case 'TK_STYLE': - case 'TK_SCRIPT': - if (multi_parser.token_text !== '') { - multi_parser.print_newline(false, multi_parser.output); - var text = multi_parser.token_text, - _beautifier, - script_indent_level = 1; - if (multi_parser.token_type === 'TK_SCRIPT') { - _beautifier = typeof js_beautify === 'function' && js_beautify; - } else if (multi_parser.token_type === 'TK_STYLE') { - _beautifier = typeof css_beautify === 'function' && css_beautify; - } - - if (options.indent_scripts === "keep") { - script_indent_level = 0; - } else if (options.indent_scripts === "separate") { - script_indent_level = -multi_parser.indent_level; - } - - var indentation = multi_parser.get_full_indent(script_indent_level); - if (_beautifier) { - - // call the Beautifier if avaliable - var Child_options = function() { - this.eol = '\n'; - }; - Child_options.prototype = options; - var child_options = new Child_options(); - text = _beautifier(text.replace(/^\s*/, indentation), child_options); - } else { - // simply indent the string otherwise - var white = text.match(/^\s*/)[0]; - var _level = white.match(/[^\n\r]*$/)[0].split(multi_parser.indent_string).length - 1; - var reindent = multi_parser.get_full_indent(script_indent_level - _level); - text = text.replace(/^\s*/, indentation) - .replace(/\r\n|\r|\n/g, '\n' + reindent) - .replace(/\s+$/, ''); - } - if (text) { - multi_parser.print_token_raw(text); - multi_parser.print_newline(true, multi_parser.output); - } - } - multi_parser.current_mode = 'TAG'; - break; - default: - // We should not be getting here but we don't want to drop input on the floor - // Just output the text and move on - if (multi_parser.token_text !== '') { - multi_parser.print_token(multi_parser.token_text); - } - break; + if ( + tag_extracted_from_last_output === null || + (tag_extracted_from_last_output[1] !== tag_name && + !multi_parser.Utils.in_array( + tag_extracted_from_last_output[1], + unformatted + )) + ) { + multi_parser.print_newline(false, multi_parser.output); + } + } + multi_parser.print_token(multi_parser.token_text); + multi_parser.current_mode = 'CONTENT'; + break; + case 'TK_TAG_SINGLE': + // Don't add a newline before elements that should remain unformatted. + var tag_check = multi_parser.token_text.match(/^\s*<([a-z-]+)/i); + if ( + !tag_check || + !multi_parser.Utils.in_array(tag_check[1], unformatted) + ) { + multi_parser.print_newline(false, multi_parser.output); + } + multi_parser.print_token(multi_parser.token_text); + multi_parser.current_mode = 'CONTENT'; + break; + case 'TK_TAG_HANDLEBARS_ELSE': + multi_parser.print_token(multi_parser.token_text); + if (multi_parser.indent_content) { + multi_parser.indent(); + multi_parser.indent_content = false; + } + multi_parser.current_mode = 'CONTENT'; + break; + case 'TK_TAG_HANDLEBARS_COMMENT': + multi_parser.print_token(multi_parser.token_text); + multi_parser.current_mode = 'TAG'; + break; + case 'TK_CONTENT': + multi_parser.print_token(multi_parser.token_text); + multi_parser.current_mode = 'TAG'; + break; + case 'TK_STYLE': + case 'TK_SCRIPT': + if (multi_parser.token_text !== '') { + multi_parser.print_newline(false, multi_parser.output); + var text = multi_parser.token_text, + _beautifier, + script_indent_level = 1; + if (multi_parser.token_type === 'TK_SCRIPT') { + _beautifier = typeof js_beautify === 'function' && js_beautify; + } else if (multi_parser.token_type === 'TK_STYLE') { + _beautifier = typeof css_beautify === 'function' && css_beautify; } - multi_parser.last_token = multi_parser.token_type; - multi_parser.last_text = multi_parser.token_text; - } - var sweet_code = multi_parser.output.join('').replace(/[\r\n\t ]+$/, ''); - - // establish end_with_newline - if (end_with_newline) { - sweet_code += '\n'; - } - if (eol != '\n') { - sweet_code = sweet_code.replace(/[\n]/g, eol); - } + if (options.indent_scripts === 'keep') { + script_indent_level = 0; + } else if (options.indent_scripts === 'separate') { + script_indent_level = -multi_parser.indent_level; + } - return sweet_code; + var indentation = multi_parser.get_full_indent(script_indent_level); + if (_beautifier) { + // call the Beautifier if avaliable + var Child_options = function() { + this.eol = '\n'; + }; + Child_options.prototype = options; + var child_options = new Child_options(); + text = _beautifier( + text.replace(/^\s*/, indentation), + child_options + ); + } else { + // simply indent the string otherwise + var white = text.match(/^\s*/)[0]; + var _level = + white.match(/[^\n\r]*$/)[0].split(multi_parser.indent_string) + .length - 1; + var reindent = multi_parser.get_full_indent( + script_indent_level - _level + ); + text = text + .replace(/^\s*/, indentation) + .replace(/\r\n|\r|\n/g, '\n' + reindent) + .replace(/\s+$/, ''); + } + if (text) { + multi_parser.print_token_raw(text); + multi_parser.print_newline(true, multi_parser.output); + } + } + multi_parser.current_mode = 'TAG'; + break; + default: + // We should not be getting here but we don't want to drop input on the floor + // Just output the text and move on + if (multi_parser.token_text !== '') { + multi_parser.print_token(multi_parser.token_text); + } + break; + } + multi_parser.last_token = multi_parser.token_type; + multi_parser.last_text = multi_parser.token_text; } + var sweet_code = multi_parser.output.join('').replace(/[\r\n\t ]+$/, ''); - if (typeof define === "function" && define.amd) { - // Add support for AMD ( https://github.com/amdjs/amdjs-api/wiki/AMD#defineamd-property- ) - define(["require", "./beautify", "./beautify-css"], function(requireamd) { - var js_beautify = requireamd("./beautify"); - var css_beautify = requireamd("./beautify-css"); + // establish end_with_newline + if (end_with_newline) { + sweet_code += '\n'; + } - return { - html_beautify: function(html_source, options) { - return style_html(html_source, options, js_beautify.js_beautify, css_beautify.css_beautify); - } - }; - }); - } else if (typeof exports !== "undefined") { - // Add support for CommonJS. Just put this file somewhere on your require.paths - // and you will be able to `var html_beautify = require("beautify").html_beautify`. - var js_beautify = require('./beautify.js'); - var css_beautify = require('./beautify-css.js'); - - exports.html_beautify = function(html_source, options) { - return style_html(html_source, options, js_beautify.js_beautify, css_beautify.css_beautify); - }; - } else if (typeof window !== "undefined") { - // If we're running a web page and don't have either of the above, add our one global - window.html_beautify = function(html_source, options) { - return style_html(html_source, options, window.js_beautify, window.css_beautify); - }; - } else if (typeof global !== "undefined") { - // If we don't even have window, try global. - global.html_beautify = function(html_source, options) { - return style_html(html_source, options, global.js_beautify, global.css_beautify); - }; + if (eol != '\n') { + sweet_code = sweet_code.replace(/[\n]/g, eol); } -}()); + return sweet_code; + } + + if (typeof define === 'function' && define.amd) { + // Add support for AMD ( https://github.com/amdjs/amdjs-api/wiki/AMD#defineamd-property- ) + define(['require', './beautify', './beautify-css'], function(requireamd) { + var js_beautify = requireamd('./beautify'); + var css_beautify = requireamd('./beautify-css'); + + return { + html_beautify: function(html_source, options) { + return style_html( + html_source, + options, + js_beautify.js_beautify, + css_beautify.css_beautify + ); + } + }; + }); + } else if (typeof exports !== 'undefined') { + // Add support for CommonJS. Just put this file somewhere on your require.paths + // and you will be able to `var html_beautify = require("beautify").html_beautify`. + var js_beautify = require('./beautify.js'); + var css_beautify = require('./beautify-css.js'); + + exports.html_beautify = function(html_source, options) { + return style_html( + html_source, + options, + js_beautify.js_beautify, + css_beautify.css_beautify + ); + }; + } else if (typeof window !== 'undefined') { + // If we're running a web page and don't have either of the above, add our one global + window.html_beautify = function(html_source, options) { + return style_html( + html_source, + options, + window.js_beautify, + window.css_beautify + ); + }; + } else if (typeof global !== 'undefined') { + // If we don't even have window, try global. + global.html_beautify = function(html_source, options) { + return style_html( + html_source, + options, + global.js_beautify, + global.css_beautify + ); + }; + } +})(); diff --git a/fec/fec/static/js/vendor/jquery.htmlClean.min.js b/fec/fec/static/js/vendor/jquery.htmlClean.min.js index ed7b7f0be1..55767a5446 100644 --- a/fec/fec/static/js/vendor/jquery.htmlClean.min.js +++ b/fec/fec/static/js/vendor/jquery.htmlClean.min.js @@ -1 +1,720 @@ -(function($){$.fn.htmlClean=function(options){return this.each(function(){if(this.value){this.value=$.htmlClean(this.value,options)}else{this.innerHTML=$.htmlClean(this.innerHTML,options)}})};$.htmlClean=function(html,options){options=$.extend({},$.htmlClean.defaults,options);options.allowEmpty=tagAllowEmpty.concat(options.allowEmpty);var tagsRE=/(<(\/)?(\w+:)?([\w]+)([^>]*)>)|<!--(.*?--)>/gi;var attrsRE=/([\w\-]+)\s*=\s*(".*?"|'.*?'|[^\s>\/]*)/gi;var tagMatch;var root=new Element();var stack=[root];var container=root;if(options.bodyOnly){if(tagMatch=/<body[^>]*>((\n|.)*)<\/body>/i.exec(html)){html=tagMatch[1]}}html=html.concat("<xxx>");var lastIndex;while(tagMatch=tagsRE.exec(html)){var tag=tagMatch[6]?new Tag("--",null,tagMatch[6],options):new Tag(tagMatch[4],tagMatch[2],tagMatch[5],options);var text=html.substring(lastIndex,tagMatch.index);if(text.length>0){var child=container.children[container.children.length-1];if(container.children.length>0&&isText(child=container.children[container.children.length-1])){container.children[container.children.length-1]=child.concat(text)}else{container.children.push(text)}}lastIndex=tagsRE.lastIndex;if(tag.isClosing){if(popToTagName(stack,[tag.name])){stack.pop();container=stack[stack.length-1]}}else{var element=new Element(tag);var attrMatch;while(attrMatch=attrsRE.exec(tag.rawAttributes)){if(attrMatch[1].toLowerCase()=="style"&&options.replaceStyles){var renderParent=!tag.isInline;for(var i=0;i<options.replaceStyles.length;i++){if(options.replaceStyles[i][0].test(attrMatch[2])){if(!renderParent){tag.render=false;renderParent=true}container.children.push(element);stack.push(element);container=element;tag=new Tag(options.replaceStyles[i][1],"","",options);element=new Element(tag)}}}if(tag.allowedAttributes!=null&&(tag.allowedAttributes.length==0||$.inArray(attrMatch[1],tag.allowedAttributes)>-1)){element.attributes.push(new Attribute(attrMatch[1],attrMatch[2]))}}$.each(tag.requiredAttributes,function(){var name=this.toString();if(!element.hasAttribute(name)){element.attributes.push(new Attribute(name,""))}});for(var repIndex=0;repIndex<options.replace.length;repIndex++){for(var tagIndex=0;tagIndex<options.replace[repIndex][0].length;tagIndex++){var byName=typeof(options.replace[repIndex][0][tagIndex])=="string";if((byName&&options.replace[repIndex][0][tagIndex]==tag.name)||(!byName&&options.replace[repIndex][0][tagIndex].test(tagMatch))){tag.rename(options.replace[repIndex][1]);repIndex=options.replace.length;break}}}var add=true;if(!container.isRoot){if(container.tag.isInline&&!tag.isInline){if(add=popToContainer(stack)){container=stack[stack.length-1]}}else{if(container.tag.disallowNest&&tag.disallowNest&&!tag.requiredParent){add=false}else{if(tag.requiredParent){if(add=popToTagName(stack,tag.requiredParent)){container=stack[stack.length-1]}}}}}if(add){container.children.push(element);if(tag.toProtect){var tagMatch2;while(tagMatch2=tagsRE.exec(html)){var tag2=new Tag(tagMatch2[4],tagMatch2[1],tagMatch2[5],options);if(tag2.isClosing&&tag2.name==tag.name){element.children.push(RegExp.leftContext.substring(lastIndex));lastIndex=tagsRE.lastIndex;break}}}else{if(!tag.isSelfClosing&&!tag.isNonClosing){stack.push(element);container=element}}}}}return $.htmlClean.trim(render(root,options).join(""))};$.htmlClean.defaults={bodyOnly:true,allowedTags:[],removeTags:["basefont","center","dir","font","frame","frameset","iframe","isindex","menu","noframes","s","strike","u"],allowedAttributes:[],removeAttrs:[],allowedClasses:[],format:false,formatIndent:0,replace:[[["b","big"],"strong"],[["i"],"em"]],replaceStyles:[[/font-weight:\s*bold/i,"strong"],[/font-style:\s*italic/i,"em"],[/vertical-align:\s*super/i,"sup"],[/vertical-align:\s*sub/i,"sub"]],allowComments:false,allowEmpty:[]};function applyFormat(element,options,output,indent){if(element.tag.format&&output.length>0){output.push("\n");for(var i=0;i<indent;i++){output.push("\t")}}}function render(element,options){var output=[],empty=element.attributes.length==0,indent=0;if(element.tag.isComment){if(options.allowComments){output.push("<!--");output.push(element.tag.rawAttributes);output.push(">");if(options.format){applyFormat(element,options,output,indent-1)}}}else{var renderTag=element.tag.render&&(options.allowedTags.length==0||$.inArray(element.tag.name,options.allowedTags)>-1)&&(options.removeTags.length==0||$.inArray(element.tag.name,options.removeTags)==-1);if(!element.isRoot&&renderTag){output.push("<");output.push(element.tag.name);$.each(element.attributes,function(){if($.inArray(this.name,options.removeAttrs)==-1){var m=RegExp(/^(['"]?)(.*?)['"]?$/).exec(this.value);var value=m[2];var valueQuote=m[1]||"'";if(this.name=="class"&&options.allowedClasses.length>0){value=$.grep(value.split(" "),function(c){return $.grep(options.allowedClasses,function(a){return a==c||(a[0]==c&&(a.length==1||$.inArray(element.tag.name,a[1])>-1))}).length>0}).join(" ")}if(value!=null&&(value.length>0||$.inArray(this.name,element.tag.requiredAttributes)>-1)){output.push(" ");output.push(this.name);output.push("=");output.push(valueQuote);output.push(value);output.push(valueQuote)}}})}if(element.tag.isSelfClosing){if(renderTag){output.push(" />")}empty=false}else{if(element.tag.isNonClosing){empty=false}else{if(!element.isRoot&&renderTag){output.push(">")}indent=options.formatIndent++;if(element.tag.toProtect){outputChildren=$.htmlClean.trim(element.children.join("")).replace(/<br>/ig,"\n");output.push(outputChildren);empty=outputChildren.length==0}else{var outputChildren=[];for(var i=0;i<element.children.length;i++){var child=element.children[i];var text=$.htmlClean.trim(textClean(isText(child)?child:child.childrenToString()));if(isInline(child)){if(i>0&&text.length>0&&(startsWithWhitespace(child)||endsWithWhitespace(element.children[i-1]))){outputChildren.push(" ")}}if(isText(child)){if(text.length>0){outputChildren.push(text)}}else{if(i!=element.children.length-1||child.tag.name!="br"){if(options.format){applyFormat(child,options,outputChildren,indent)}outputChildren=outputChildren.concat(render(child,options))}}}options.formatIndent--;if(outputChildren.length>0){if(options.format&&outputChildren[0]!="\n"){applyFormat(element,options,output,indent)}output=output.concat(outputChildren);empty=false}}if(!element.isRoot&&renderTag){if(options.format){applyFormat(element,options,output,indent-1)}output.push("</");output.push(element.tag.name);output.push(">")}}}if(!element.tag.allowEmpty&&empty){return[]}}return output}function popToTagName(stack,tagNameArray){return pop(stack,function(element){return $.inArray(element.tag.nameOriginal,tagNameArray)>-1})}function popToContainer(stack){return pop(stack,function(element){return element.isRoot||!element.tag.isInline})}function pop(stack,test,index){index=index||1;var element=stack[stack.length-index];if(test(element)){return true}else{if(stack.length-index>0&&pop(stack,test,index+1)){stack.pop();return true}}return false}function Element(tag){if(tag){this.tag=tag;this.isRoot=false}else{this.tag=new Tag("root");this.isRoot=true}this.attributes=[];this.children=[];this.hasAttribute=function(name){for(var i=0;i<this.attributes.length;i++){if(this.attributes[i].name==name){return true}}return false};this.childrenToString=function(){return this.children.join("")};return this}function Attribute(name,value){this.name=name;this.value=value;return this}function Tag(name,close,rawAttributes,options){this.name=name.toLowerCase();this.nameOriginal=this.name;this.render=true;this.init=function(){if(this.name=="--"){this.isComment=true;this.isSelfClosing=true;this.format=true}else{this.isComment=false;this.isSelfClosing=$.inArray(this.name,tagSelfClosing)>-1;this.isNonClosing=$.inArray(this.name,tagNonClosing)>-1;this.isClosing=(close!=undefined&&close.length>0);this.isInline=$.inArray(this.name,tagInline)>-1;this.disallowNest=$.inArray(this.name,tagDisallowNest)>-1;this.requiredParent=tagRequiredParent[$.inArray(this.name,tagRequiredParent)+1];this.allowEmpty=options&&$.inArray(this.name,options.allowEmpty)>-1;this.toProtect=$.inArray(this.name,tagProtect)>-1;this.format=$.inArray(this.name,tagFormat)>-1||!this.isInline}this.rawAttributes=rawAttributes;this.requiredAttributes=tagAttributesRequired[$.inArray(this.name,tagAttributesRequired)+1];if(options){if(!options.tagAttributesCache){options.tagAttributesCache=[]}if($.inArray(this.name,options.tagAttributesCache)==-1){var cacheItem=tagAttributes[$.inArray(this.name,tagAttributes)+1].slice(0);for(var i=0;i<options.allowedAttributes.length;i++){var attrName=options.allowedAttributes[i][0];if((options.allowedAttributes[i].length==1||$.inArray(this.name,options.allowedAttributes[i][1])>-1)&&$.inArray(attrName,cacheItem)==-1){cacheItem.push(attrName)}}options.tagAttributesCache.push(this.name);options.tagAttributesCache.push(cacheItem)}this.allowedAttributes=options.tagAttributesCache[$.inArray(this.name,options.tagAttributesCache)+1]}};this.init();this.rename=function(newName){this.name=newName;this.init()};return this}function startsWithWhitespace(item){while(isElement(item)&&item.children.length>0){item=item.children[0]}if(!isText(item)){return false}var text=textClean(item);return text.length>0&&$.htmlClean.isWhitespace(text.charAt(0))}function endsWithWhitespace(item){while(isElement(item)&&item.children.length>0){item=item.children[item.children.length-1]}if(!isText(item)){return false}var text=textClean(item);return text.length>0&&$.htmlClean.isWhitespace(text.charAt(text.length-1))}function isText(item){return item.constructor==String}function isInline(item){return isText(item)||item.tag.isInline}function isElement(item){return item.constructor==Element}function textClean(text){return text.replace(/ |\n/g," ").replace(/\s\s+/g," ")}$.htmlClean.trim=function(text){return $.htmlClean.trimStart($.htmlClean.trimEnd(text))};$.htmlClean.trimStart=function(text){return text.substring($.htmlClean.trimStartIndex(text))};$.htmlClean.trimStartIndex=function(text){for(var start=0;start<text.length-1&&$.htmlClean.isWhitespace(text.charAt(start));start++){}return start};$.htmlClean.trimEnd=function(text){return text.substring(0,$.htmlClean.trimEndIndex(text))};$.htmlClean.trimEndIndex=function(text){for(var end=text.length-1;end>=0&&$.htmlClean.isWhitespace(text.charAt(end));end--){}return end+1};$.htmlClean.isWhitespace=function(c){return $.inArray(c,whitespace)!=-1};var tagInline=["a","abbr","acronym","address","b","big","br","button","caption","cite","code","del","em","font","hr","i","input","img","ins","label","legend","map","q","s","samp","select","option","param","small","span","strike","strong","sub","sup","tt","u","var"];var tagFormat=["address","button","caption","code","input","label","legend","select","option","param"];var tagDisallowNest=["h1","h2","h3","h4","h5","h6","p","th","td","object"];var tagAllowEmpty=["th","td"];var tagRequiredParent=[null,"li",["ul","ol"],"dt",["dl"],"dd",["dl"],"td",["tr"],"th",["tr"],"tr",["table","thead","tbody","tfoot"],"thead",["table"],"tbody",["table"],"tfoot",["table"],"param",["object"]];var tagProtect=["script","style","pre","code"];var tagSelfClosing=["area","base","br","col","command","embed","hr","img","input","keygen","link","meta","param","source","track","wbr"];var tagNonClosing=["!doctype","?xml"];var tagAttributes=[["class"],"?xml",[],"!doctype",[],"a",["accesskey","class","href","name","title","rel","rev","type","tabindex"],"abbr",["class","title"],"acronym",["class","title"],"blockquote",["cite","class"],"button",["class","disabled","name","type","value"],"del",["cite","class","datetime"],"form",["accept","action","class","enctype","method","name"],"iframe",["class","height","name","sandbox","seamless","src","srcdoc","width"],"input",["accept","accesskey","alt","checked","class","disabled","ismap","maxlength","name","size","readonly","src","tabindex","type","usemap","value"],"img",["alt","class","height","src","width"],"ins",["cite","class","datetime"],"label",["accesskey","class","for"],"legend",["accesskey","class"],"link",["href","rel","type"],"meta",["content","http-equiv","name","scheme","charset"],"map",["name"],"optgroup",["class","disabled","label"],"option",["class","disabled","label","selected","value"],"q",["class","cite"],"script",["src","type"],"select",["class","disabled","multiple","name","size","tabindex"],"style",["type"],"table",["class","summary"],"th",["class","colspan","rowspan"],"td",["class","colspan","rowspan"],"textarea",["accesskey","class","cols","disabled","name","readonly","rows","tabindex"],"param",["name","value"],"embed",["height","src","type","width"]];var tagAttributesRequired=[[],"img",["alt"]];var whitespace=[" "," ","\t","\n","\r","\f"]})(jQuery); \ No newline at end of file +(function($) { + $.fn.htmlClean = function(options) { + return this.each(function() { + if (this.value) { + this.value = $.htmlClean(this.value, options); + } else { + this.innerHTML = $.htmlClean(this.innerHTML, options); + } + }); + }; + $.htmlClean = function(html, options) { + options = $.extend({}, $.htmlClean.defaults, options); + options.allowEmpty = tagAllowEmpty.concat(options.allowEmpty); + var tagsRE = /(<(\/)?(\w+:)?([\w]+)([^>]*)>)|<!--(.*?--)>/gi; + var attrsRE = /([\w\-]+)\s*=\s*(".*?"|'.*?'|[^\s>\/]*)/gi; + var tagMatch; + var root = new Element(); + var stack = [root]; + var container = root; + if (options.bodyOnly) { + if ((tagMatch = /<body[^>]*>((\n|.)*)<\/body>/i.exec(html))) { + html = tagMatch[1]; + } + } + html = html.concat('<xxx>'); + var lastIndex; + while ((tagMatch = tagsRE.exec(html))) { + var tag = tagMatch[6] + ? new Tag('--', null, tagMatch[6], options) + : new Tag(tagMatch[4], tagMatch[2], tagMatch[5], options); + var text = html.substring(lastIndex, tagMatch.index); + if (text.length > 0) { + var child = container.children[container.children.length - 1]; + if ( + container.children.length > 0 && + isText((child = container.children[container.children.length - 1])) + ) { + container.children[container.children.length - 1] = child.concat( + text + ); + } else { + container.children.push(text); + } + } + lastIndex = tagsRE.lastIndex; + if (tag.isClosing) { + if (popToTagName(stack, [tag.name])) { + stack.pop(); + container = stack[stack.length - 1]; + } + } else { + var element = new Element(tag); + var attrMatch; + while ((attrMatch = attrsRE.exec(tag.rawAttributes))) { + if (attrMatch[1].toLowerCase() == 'style' && options.replaceStyles) { + var renderParent = !tag.isInline; + for (var i = 0; i < options.replaceStyles.length; i++) { + if (options.replaceStyles[i][0].test(attrMatch[2])) { + if (!renderParent) { + tag.render = false; + renderParent = true; + } + container.children.push(element); + stack.push(element); + container = element; + tag = new Tag(options.replaceStyles[i][1], '', '', options); + element = new Element(tag); + } + } + } + if ( + tag.allowedAttributes != null && + (tag.allowedAttributes.length == 0 || + $.inArray(attrMatch[1], tag.allowedAttributes) > -1) + ) { + element.attributes.push(new Attribute(attrMatch[1], attrMatch[2])); + } + } + $.each(tag.requiredAttributes, function() { + var name = this.toString(); + if (!element.hasAttribute(name)) { + element.attributes.push(new Attribute(name, '')); + } + }); + for (var repIndex = 0; repIndex < options.replace.length; repIndex++) { + for ( + var tagIndex = 0; + tagIndex < options.replace[repIndex][0].length; + tagIndex++ + ) { + var byName = + typeof options.replace[repIndex][0][tagIndex] == 'string'; + if ( + (byName && options.replace[repIndex][0][tagIndex] == tag.name) || + (!byName && options.replace[repIndex][0][tagIndex].test(tagMatch)) + ) { + tag.rename(options.replace[repIndex][1]); + repIndex = options.replace.length; + break; + } + } + } + var add = true; + if (!container.isRoot) { + if (container.tag.isInline && !tag.isInline) { + if ((add = popToContainer(stack))) { + container = stack[stack.length - 1]; + } + } else { + if ( + container.tag.disallowNest && + tag.disallowNest && + !tag.requiredParent + ) { + add = false; + } else { + if (tag.requiredParent) { + if ((add = popToTagName(stack, tag.requiredParent))) { + container = stack[stack.length - 1]; + } + } + } + } + } + if (add) { + container.children.push(element); + if (tag.toProtect) { + var tagMatch2; + while ((tagMatch2 = tagsRE.exec(html))) { + var tag2 = new Tag( + tagMatch2[4], + tagMatch2[1], + tagMatch2[5], + options + ); + if (tag2.isClosing && tag2.name == tag.name) { + element.children.push(RegExp.leftContext.substring(lastIndex)); + lastIndex = tagsRE.lastIndex; + break; + } + } + } else { + if (!tag.isSelfClosing && !tag.isNonClosing) { + stack.push(element); + container = element; + } + } + } + } + } + return $.htmlClean.trim(render(root, options).join('')); + }; + $.htmlClean.defaults = { + bodyOnly: true, + allowedTags: [], + removeTags: [ + 'basefont', + 'center', + 'dir', + 'font', + 'frame', + 'frameset', + 'iframe', + 'isindex', + 'menu', + 'noframes', + 's', + 'strike', + 'u' + ], + allowedAttributes: [], + removeAttrs: [], + allowedClasses: [], + format: false, + formatIndent: 0, + replace: [[['b', 'big'], 'strong'], [['i'], 'em']], + replaceStyles: [ + [/font-weight:\s*bold/i, 'strong'], + [/font-style:\s*italic/i, 'em'], + [/vertical-align:\s*super/i, 'sup'], + [/vertical-align:\s*sub/i, 'sub'] + ], + allowComments: false, + allowEmpty: [] + }; + function applyFormat(element, options, output, indent) { + if (element.tag.format && output.length > 0) { + output.push('\n'); + for (var i = 0; i < indent; i++) { + output.push('\t'); + } + } + } + function render(element, options) { + var output = [], + empty = element.attributes.length == 0, + indent = 0; + if (element.tag.isComment) { + if (options.allowComments) { + output.push('<!--'); + output.push(element.tag.rawAttributes); + output.push('>'); + if (options.format) { + applyFormat(element, options, output, indent - 1); + } + } + } else { + var renderTag = + element.tag.render && + (options.allowedTags.length == 0 || + $.inArray(element.tag.name, options.allowedTags) > -1) && + (options.removeTags.length == 0 || + $.inArray(element.tag.name, options.removeTags) == -1); + if (!element.isRoot && renderTag) { + output.push('<'); + output.push(element.tag.name); + $.each(element.attributes, function() { + if ($.inArray(this.name, options.removeAttrs) == -1) { + var m = RegExp(/^(['"]?)(.*?)['"]?$/).exec(this.value); + var value = m[2]; + var valueQuote = m[1] || "'"; + if (this.name == 'class' && options.allowedClasses.length > 0) { + value = $.grep(value.split(' '), function(c) { + return ( + $.grep(options.allowedClasses, function(a) { + return ( + a == c || + (a[0] == c && + (a.length == 1 || + $.inArray(element.tag.name, a[1]) > -1)) + ); + }).length > 0 + ); + }).join(' '); + } + if ( + value != null && + (value.length > 0 || + $.inArray(this.name, element.tag.requiredAttributes) > -1) + ) { + output.push(' '); + output.push(this.name); + output.push('='); + output.push(valueQuote); + output.push(value); + output.push(valueQuote); + } + } + }); + } + if (element.tag.isSelfClosing) { + if (renderTag) { + output.push(' />'); + } + empty = false; + } else { + if (element.tag.isNonClosing) { + empty = false; + } else { + if (!element.isRoot && renderTag) { + output.push('>'); + } + indent = options.formatIndent++; + if (element.tag.toProtect) { + outputChildren = $.htmlClean + .trim(element.children.join('')) + .replace(/<br>/gi, '\n'); + output.push(outputChildren); + empty = outputChildren.length == 0; + } else { + var outputChildren = []; + for (var i = 0; i < element.children.length; i++) { + var child = element.children[i]; + var text = $.htmlClean.trim( + textClean(isText(child) ? child : child.childrenToString()) + ); + if (isInline(child)) { + if ( + i > 0 && + text.length > 0 && + (startsWithWhitespace(child) || + endsWithWhitespace(element.children[i - 1])) + ) { + outputChildren.push(' '); + } + } + if (isText(child)) { + if (text.length > 0) { + outputChildren.push(text); + } + } else { + if ( + i != element.children.length - 1 || + child.tag.name != 'br' + ) { + if (options.format) { + applyFormat(child, options, outputChildren, indent); + } + outputChildren = outputChildren.concat( + render(child, options) + ); + } + } + } + options.formatIndent--; + if (outputChildren.length > 0) { + if (options.format && outputChildren[0] != '\n') { + applyFormat(element, options, output, indent); + } + output = output.concat(outputChildren); + empty = false; + } + } + if (!element.isRoot && renderTag) { + if (options.format) { + applyFormat(element, options, output, indent - 1); + } + output.push('</'); + output.push(element.tag.name); + output.push('>'); + } + } + } + if (!element.tag.allowEmpty && empty) { + return []; + } + } + return output; + } + function popToTagName(stack, tagNameArray) { + return pop(stack, function(element) { + return $.inArray(element.tag.nameOriginal, tagNameArray) > -1; + }); + } + function popToContainer(stack) { + return pop(stack, function(element) { + return element.isRoot || !element.tag.isInline; + }); + } + function pop(stack, test, index) { + index = index || 1; + var element = stack[stack.length - index]; + if (test(element)) { + return true; + } else { + if (stack.length - index > 0 && pop(stack, test, index + 1)) { + stack.pop(); + return true; + } + } + return false; + } + function Element(tag) { + if (tag) { + this.tag = tag; + this.isRoot = false; + } else { + this.tag = new Tag('root'); + this.isRoot = true; + } + this.attributes = []; + this.children = []; + this.hasAttribute = function(name) { + for (var i = 0; i < this.attributes.length; i++) { + if (this.attributes[i].name == name) { + return true; + } + } + return false; + }; + this.childrenToString = function() { + return this.children.join(''); + }; + return this; + } + function Attribute(name, value) { + this.name = name; + this.value = value; + return this; + } + function Tag(name, close, rawAttributes, options) { + this.name = name.toLowerCase(); + this.nameOriginal = this.name; + this.render = true; + this.init = function() { + if (this.name == '--') { + this.isComment = true; + this.isSelfClosing = true; + this.format = true; + } else { + this.isComment = false; + this.isSelfClosing = $.inArray(this.name, tagSelfClosing) > -1; + this.isNonClosing = $.inArray(this.name, tagNonClosing) > -1; + this.isClosing = close != undefined && close.length > 0; + this.isInline = $.inArray(this.name, tagInline) > -1; + this.disallowNest = $.inArray(this.name, tagDisallowNest) > -1; + this.requiredParent = + tagRequiredParent[$.inArray(this.name, tagRequiredParent) + 1]; + this.allowEmpty = + options && $.inArray(this.name, options.allowEmpty) > -1; + this.toProtect = $.inArray(this.name, tagProtect) > -1; + this.format = $.inArray(this.name, tagFormat) > -1 || !this.isInline; + } + this.rawAttributes = rawAttributes; + this.requiredAttributes = + tagAttributesRequired[$.inArray(this.name, tagAttributesRequired) + 1]; + if (options) { + if (!options.tagAttributesCache) { + options.tagAttributesCache = []; + } + if ($.inArray(this.name, options.tagAttributesCache) == -1) { + var cacheItem = tagAttributes[ + $.inArray(this.name, tagAttributes) + 1 + ].slice(0); + for (var i = 0; i < options.allowedAttributes.length; i++) { + var attrName = options.allowedAttributes[i][0]; + if ( + (options.allowedAttributes[i].length == 1 || + $.inArray(this.name, options.allowedAttributes[i][1]) > -1) && + $.inArray(attrName, cacheItem) == -1 + ) { + cacheItem.push(attrName); + } + } + options.tagAttributesCache.push(this.name); + options.tagAttributesCache.push(cacheItem); + } + this.allowedAttributes = + options.tagAttributesCache[ + $.inArray(this.name, options.tagAttributesCache) + 1 + ]; + } + }; + this.init(); + this.rename = function(newName) { + this.name = newName; + this.init(); + }; + return this; + } + function startsWithWhitespace(item) { + while (isElement(item) && item.children.length > 0) { + item = item.children[0]; + } + if (!isText(item)) { + return false; + } + var text = textClean(item); + return text.length > 0 && $.htmlClean.isWhitespace(text.charAt(0)); + } + function endsWithWhitespace(item) { + while (isElement(item) && item.children.length > 0) { + item = item.children[item.children.length - 1]; + } + if (!isText(item)) { + return false; + } + var text = textClean(item); + return ( + text.length > 0 && $.htmlClean.isWhitespace(text.charAt(text.length - 1)) + ); + } + function isText(item) { + return item.constructor == String; + } + function isInline(item) { + return isText(item) || item.tag.isInline; + } + function isElement(item) { + return item.constructor == Element; + } + function textClean(text) { + return text.replace(/ |\n/g, ' ').replace(/\s\s+/g, ' '); + } + $.htmlClean.trim = function(text) { + return $.htmlClean.trimStart($.htmlClean.trimEnd(text)); + }; + $.htmlClean.trimStart = function(text) { + return text.substring($.htmlClean.trimStartIndex(text)); + }; + $.htmlClean.trimStartIndex = function(text) { + for ( + var start = 0; + start < text.length - 1 && $.htmlClean.isWhitespace(text.charAt(start)); + start++ + ) {} + return start; + }; + $.htmlClean.trimEnd = function(text) { + return text.substring(0, $.htmlClean.trimEndIndex(text)); + }; + $.htmlClean.trimEndIndex = function(text) { + for ( + var end = text.length - 1; + end >= 0 && $.htmlClean.isWhitespace(text.charAt(end)); + end-- + ) {} + return end + 1; + }; + $.htmlClean.isWhitespace = function(c) { + return $.inArray(c, whitespace) != -1; + }; + var tagInline = [ + 'a', + 'abbr', + 'acronym', + 'address', + 'b', + 'big', + 'br', + 'button', + 'caption', + 'cite', + 'code', + 'del', + 'em', + 'font', + 'hr', + 'i', + 'input', + 'img', + 'ins', + 'label', + 'legend', + 'map', + 'q', + 's', + 'samp', + 'select', + 'option', + 'param', + 'small', + 'span', + 'strike', + 'strong', + 'sub', + 'sup', + 'tt', + 'u', + 'var' + ]; + var tagFormat = [ + 'address', + 'button', + 'caption', + 'code', + 'input', + 'label', + 'legend', + 'select', + 'option', + 'param' + ]; + var tagDisallowNest = [ + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'p', + 'th', + 'td', + 'object' + ]; + var tagAllowEmpty = ['th', 'td']; + var tagRequiredParent = [ + null, + 'li', + ['ul', 'ol'], + 'dt', + ['dl'], + 'dd', + ['dl'], + 'td', + ['tr'], + 'th', + ['tr'], + 'tr', + ['table', 'thead', 'tbody', 'tfoot'], + 'thead', + ['table'], + 'tbody', + ['table'], + 'tfoot', + ['table'], + 'param', + ['object'] + ]; + var tagProtect = ['script', 'style', 'pre', 'code']; + var tagSelfClosing = [ + 'area', + 'base', + 'br', + 'col', + 'command', + 'embed', + 'hr', + 'img', + 'input', + 'keygen', + 'link', + 'meta', + 'param', + 'source', + 'track', + 'wbr' + ]; + var tagNonClosing = ['!doctype', '?xml']; + var tagAttributes = [ + ['class'], + '?xml', + [], + '!doctype', + [], + 'a', + [ + 'accesskey', + 'class', + 'href', + 'name', + 'title', + 'rel', + 'rev', + 'type', + 'tabindex' + ], + 'abbr', + ['class', 'title'], + 'acronym', + ['class', 'title'], + 'blockquote', + ['cite', 'class'], + 'button', + ['class', 'disabled', 'name', 'type', 'value'], + 'del', + ['cite', 'class', 'datetime'], + 'form', + ['accept', 'action', 'class', 'enctype', 'method', 'name'], + 'iframe', + [ + 'class', + 'height', + 'name', + 'sandbox', + 'seamless', + 'src', + 'srcdoc', + 'width' + ], + 'input', + [ + 'accept', + 'accesskey', + 'alt', + 'checked', + 'class', + 'disabled', + 'ismap', + 'maxlength', + 'name', + 'size', + 'readonly', + 'src', + 'tabindex', + 'type', + 'usemap', + 'value' + ], + 'img', + ['alt', 'class', 'height', 'src', 'width'], + 'ins', + ['cite', 'class', 'datetime'], + 'label', + ['accesskey', 'class', 'for'], + 'legend', + ['accesskey', 'class'], + 'link', + ['href', 'rel', 'type'], + 'meta', + ['content', 'http-equiv', 'name', 'scheme', 'charset'], + 'map', + ['name'], + 'optgroup', + ['class', 'disabled', 'label'], + 'option', + ['class', 'disabled', 'label', 'selected', 'value'], + 'q', + ['class', 'cite'], + 'script', + ['src', 'type'], + 'select', + ['class', 'disabled', 'multiple', 'name', 'size', 'tabindex'], + 'style', + ['type'], + 'table', + ['class', 'summary'], + 'th', + ['class', 'colspan', 'rowspan'], + 'td', + ['class', 'colspan', 'rowspan'], + 'textarea', + [ + 'accesskey', + 'class', + 'cols', + 'disabled', + 'name', + 'readonly', + 'rows', + 'tabindex' + ], + 'param', + ['name', 'value'], + 'embed', + ['height', 'src', 'type', 'width'] + ]; + var tagAttributesRequired = [[], 'img', ['alt']]; + var whitespace = [' ', ' ', '\t', '\n', '\r', '\f']; +})(jQuery); diff --git a/fec/fec/static/js/vendor/tablist.js b/fec/fec/static/js/vendor/tablist.js index a5588f46d1..005bfecedc 100644 --- a/fec/fec/static/js/vendor/tablist.js +++ b/fec/fec/static/js/vendor/tablist.js @@ -21,7 +21,7 @@ function show($target, push) { 'aria-selected': null }); $target.attr({ - 'aria-selected': 'true', + 'aria-selected': 'true' }); // Toggle panels @@ -37,12 +37,14 @@ function show($target, push) { URI.parseQuery(window.location.search), _.object([[name, value]]) ); - var search = URI('').query(query).toString(); + var search = URI('') + .query(query) + .toString(); window.history.pushState(query, search, search || window.location.pathname); analytics.pageView(); } - events.emit('tabs.show.' + value, {$tab: $target, $panel: $panel}); + events.emit('tabs.show.' + value, { $tab: $target, $panel: $panel }); } function refreshTabs() { @@ -50,9 +52,9 @@ function refreshTabs() { $('ul[role="tablist"]').each(function(index, tabs) { var $tabs = $(tabs); var name = $tabs.attr('data-name'); - var $target = query[name] ? - $tabs.find('[role="tab"][data-name="' + query[name] + '"]') : - $tabs.find('[role="tab"]').eq(0); + var $target = query[name] + ? $tabs.find('[role="tab"][data-name="' + query[name] + '"]') + : $tabs.find('[role="tab"]').eq(0); if ($target.length) { show($target); } else { @@ -85,5 +87,5 @@ function init() { module.exports = { onShow: onShow, - init: init, + init: init }; diff --git a/package-lock.json b/package-lock.json index 1af45325f4..524e4c6df7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -94,6 +94,15 @@ } } }, + "acorn-jsx": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-4.1.1.tgz", + "integrity": "sha512-JY+iV6r+cO21KtntVvFkD+iqjtdpRUpGqKWgfkCdZq1R+kbreEl8EcdcJR4SmiIgsIQT33s6QzheQ9a275Q8xw==", + "dev": true, + "requires": { + "acorn": "5.5.0" + } + }, "acorn-node": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.3.0.tgz", @@ -176,6 +185,12 @@ "ansi-wrap": "0.1.0" } }, + "ansi-escapes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", + "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", + "dev": true + }, "ansi-gray": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", @@ -352,6 +367,15 @@ "integrity": "sha1-42jqFfibxwaff/uJrsOmx9SsItQ=", "dev": true }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "1.0.3" + } + }, "array-uniq": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", @@ -1990,12 +2014,27 @@ "integrity": "sha1-0JxLUoAKpMB44t2BqGmqyQ0uVOc=", "dev": true }, + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "dev": true, + "requires": { + "callsites": "0.2.0" + } + }, "callsite": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", "dev": true }, + "callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true + }, "camel-case": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-1.2.2.tgz", @@ -2093,6 +2132,12 @@ "upper-case-first": "1.1.2" } }, + "chardet": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", + "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", + "dev": true + }, "check-types": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/check-types/-/check-types-7.3.0.tgz", @@ -2145,6 +2190,12 @@ "safe-buffer": "5.1.1" } }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + }, "clap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/clap/-/clap-1.2.3.tgz", @@ -2309,6 +2360,21 @@ } } }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "2.0.0" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, "cliui": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", @@ -2577,6 +2643,12 @@ "integrity": "sha1-kld9tSe6bEzwpFaNhLwDH0QeIfI=", "dev": true }, + "contains-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", + "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", + "dev": true + }, "content-disposition": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", @@ -3004,6 +3076,21 @@ "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", "dev": true }, + "del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "dev": true, + "requires": { + "globby": "5.0.0", + "is-path-cwd": "1.0.0", + "is-path-in-cwd": "1.0.1", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "rimraf": "2.6.2" + } + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -3151,6 +3238,15 @@ "integrity": "sha1-44Mx8IRLukm5qctxx3FYWqsbxlo=", "dev": true }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "2.0.2" + } + }, "dom-serialize": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", @@ -3753,11 +3849,476 @@ "estraverse": "4.2.0" } }, + "eslint": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.3.0.tgz", + "integrity": "sha512-N/tCqlMKkyNvAvLu+zI9AqDasnSLt00K+Hu8kdsERliC9jYEc8ck12XtjvOXrBKu8fK6RrBcN9bat6Xk++9jAg==", + "dev": true, + "requires": { + "ajv": "6.5.2", + "babel-code-frame": "6.26.0", + "chalk": "2.4.1", + "cross-spawn": "6.0.5", + "debug": "3.1.0", + "doctrine": "2.1.0", + "eslint-scope": "4.0.0", + "eslint-utils": "1.3.1", + "eslint-visitor-keys": "1.0.0", + "espree": "4.0.0", + "esquery": "1.0.1", + "esutils": "2.0.2", + "file-entry-cache": "2.0.0", + "functional-red-black-tree": "1.0.1", + "glob": "7.1.2", + "globals": "11.7.0", + "ignore": "4.0.5", + "imurmurhash": "0.1.4", + "inquirer": "5.2.0", + "is-resolvable": "1.1.0", + "js-yaml": "3.12.0", + "json-stable-stringify-without-jsonify": "1.0.1", + "levn": "0.3.0", + "lodash": "4.17.5", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "natural-compare": "1.4.0", + "optionator": "0.8.2", + "path-is-inside": "1.0.2", + "pluralize": "7.0.0", + "progress": "2.0.0", + "regexpp": "2.0.0", + "require-uncached": "1.0.3", + "semver": "5.5.0", + "string.prototype.matchall": "2.0.0", + "strip-ansi": "4.0.0", + "strip-json-comments": "2.0.1", + "table": "4.0.3", + "text-table": "0.2.0" + }, + "dependencies": { + "ajv": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.2.tgz", + "integrity": "sha512-hOs7GfvI6tUI1LfZddH82ky6mOMyTuY0mk7kE2pWpmhhUSkumzaTO5vbVwij39MdwPQWCV4Zv57Eo06NtL/GVA==", + "dev": true, + "requires": { + "fast-deep-equal": "2.0.1", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.4.1", + "uri-js": "4.2.2" + } + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.4.0" + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "1.0.4", + "path-key": "2.0.1", + "semver": "5.5.0", + "shebang-command": "1.2.0", + "which": "1.3.0" + } + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "globals": { + "version": "11.7.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.7.0.tgz", + "integrity": "sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "js-yaml": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", + "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", + "dev": true, + "requires": { + "argparse": "1.0.10", + "esprima": "4.0.1" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "progress": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", + "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", + "dev": true + }, + "string.prototype.matchall": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-2.0.0.tgz", + "integrity": "sha512-WoZ+B2ypng1dp4iFLF2kmZlwwlE19gmjgKuhL1FJfDgCREWb3ye3SDVHSzLH6bxfnvYmkCxbzkmWcQZHA4P//Q==", + "dev": true, + "requires": { + "define-properties": "1.1.2", + "es-abstract": "1.12.0", + "function-bind": "1.1.1", + "has-symbols": "1.0.0", + "regexp.prototype.flags": "1.2.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + } + } + }, + "eslint-config-prettier": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-2.9.0.tgz", + "integrity": "sha512-ag8YEyBXsm3nmOv1Hz991VtNNDMRa+MNy8cY47Pl4bw6iuzqKbJajXdqUpiw13STdLLrznxgm1hj9NhxeOYq0A==", + "dev": true, + "requires": { + "get-stdin": "5.0.1" + }, + "dependencies": { + "get-stdin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz", + "integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g=", + "dev": true + } + } + }, + "eslint-import-resolver-node": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz", + "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==", + "dev": true, + "requires": { + "debug": "2.6.9", + "resolve": "1.5.0" + } + }, + "eslint-loader": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-loader/-/eslint-loader-2.1.0.tgz", + "integrity": "sha512-f4A/Yk7qF+HcFSz5Tck2QoKIwJVHlX0soJk5MkROYahb5uvspad5Ba60rrz4u/V2/MEj1dtp/uBi6LlLWVaY7Q==", + "dev": true, + "requires": { + "loader-fs-cache": "1.0.1", + "loader-utils": "1.1.0", + "object-assign": "4.1.1", + "object-hash": "1.3.0", + "rimraf": "2.6.2" + } + }, + "eslint-module-utils": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.2.0.tgz", + "integrity": "sha1-snA2LNiLGkitMIl2zn+lTphBF0Y=", + "dev": true, + "requires": { + "debug": "2.6.9", + "pkg-dir": "1.0.0" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "2.0.1" + } + }, + "pkg-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", + "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", + "dev": true, + "requires": { + "find-up": "1.1.2" + } + } + } + }, + "eslint-plugin-import": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.13.0.tgz", + "integrity": "sha512-t6hGKQDMIt9N8R7vLepsYXgDfeuhp6ZJSgtrLEDxonpSubyxUZHjhm6LsAaZX8q6GYVxkbT3kTsV9G5mBCFR6A==", + "dev": true, + "requires": { + "contains-path": "0.1.0", + "debug": "2.6.9", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "0.3.2", + "eslint-module-utils": "2.2.0", + "has": "1.0.1", + "lodash": "4.17.5", + "minimatch": "3.0.4", + "read-pkg-up": "2.0.0", + "resolve": "1.8.1" + }, + "dependencies": { + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "requires": { + "esutils": "2.0.2", + "isarray": "1.0.0" + } + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "parse-json": "2.2.0", + "pify": "2.3.0", + "strip-bom": "3.0.0" + } + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "2.3.0" + } + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "2.0.0", + "normalize-package-data": "2.4.0", + "path-type": "2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "2.1.0", + "read-pkg": "2.0.0" + } + }, + "resolve": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", + "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", + "dev": true, + "requires": { + "path-parse": "1.0.5" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + } + } + }, + "eslint-plugin-no-unsanitized": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-no-unsanitized/-/eslint-plugin-no-unsanitized-3.0.2.tgz", + "integrity": "sha512-JnwpoH8Sv4QOjrTDutENBHzSnyYtspdjtglYtqUtAHe6f6LLKqykJle+UwFPg23GGwt5hI3amS9CRDezW8GAww==", + "dev": true + }, + "eslint-plugin-prettier": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-2.6.2.tgz", + "integrity": "sha512-tGek5clmW5swrAx1mdPYM8oThrBE83ePh7LeseZHBWfHVGrHPhKn7Y5zgRMbU/9D5Td9K4CEmUPjGxA7iw98Og==", + "dev": true, + "requires": { + "fast-diff": "1.1.2", + "jest-docblock": "21.2.0" + } + }, + "eslint-plugin-react": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.10.0.tgz", + "integrity": "sha512-18rzWn4AtbSUxFKKM7aCVcj5LXOhOKdwBino3KKWy4psxfPW0YtIbE8WNRDUdyHFL50BeLb6qFd4vpvNYyp7hw==", + "dev": true, + "requires": { + "doctrine": "2.1.0", + "has": "1.0.3", + "jsx-ast-utils": "2.0.1", + "prop-types": "15.6.2" + }, + "dependencies": { + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "1.1.1" + } + }, + "prop-types": { + "version": "15.6.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", + "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", + "dev": true, + "requires": { + "loose-envify": "1.3.1", + "object-assign": "4.1.1" + } + } + } + }, + "eslint-scope": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", + "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", + "dev": true, + "requires": { + "esrecurse": "4.2.1", + "estraverse": "4.2.0" + } + }, + "eslint-utils": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", + "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", + "dev": true + }, + "eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", + "dev": true + }, + "espree": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-4.0.0.tgz", + "integrity": "sha512-kapdTCt1bjmspxStVKX6huolXVV5ZfyZguY1lcfhVVZstce3bqxH9mcLzNn3/mlgW6wQ732+0fuG9v7h0ZQoKg==", + "dev": true, + "requires": { + "acorn": "5.7.1", + "acorn-jsx": "4.1.1" + }, + "dependencies": { + "acorn": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.1.tgz", + "integrity": "sha512-d+nbxBUGKg7Arpsvbnlq61mc12ek3EY8EQldM3GPAhWJ1UVxC6TDGbIvUMNU6obBX3i1+ptCIzV4vq0gFPEGVQ==", + "dev": true + } + } + }, "esprima": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=" }, + "esquery": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "dev": true, + "requires": { + "estraverse": "4.2.0" + } + }, "esrecurse": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", @@ -4047,6 +4608,17 @@ } } }, + "external-editor": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", + "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", + "dev": true, + "requires": { + "chardet": "0.4.2", + "iconv-lite": "0.4.19", + "tmp": "0.0.33" + } + }, "extglob": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", @@ -4163,6 +4735,12 @@ "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", "dev": true }, + "fast-diff": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.1.2.tgz", + "integrity": "sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==", + "dev": true + }, "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", @@ -4210,6 +4788,25 @@ "pend": "1.2.0" } }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "1.0.5" + } + }, + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "dev": true, + "requires": { + "flat-cache": "1.3.0", + "object-assign": "4.1.1" + } + }, "filename-regex": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", @@ -4628,6 +5225,18 @@ "integrity": "sha1-Tnmumy6zi/hrO7Vr8+ClaqX8q9c=", "dev": true }, + "flat-cache": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", + "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", + "dev": true, + "requires": { + "circular-json": "0.3.3", + "del": "2.2.2", + "graceful-fs": "4.1.11", + "write": "0.2.1" + } + }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -5790,6 +6399,12 @@ "is-callable": "1.1.4" } }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, "gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", @@ -6019,6 +6634,36 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", "integrity": "sha1-qjiWs+abSH8X4x7SFD1pqOMMLYo=" }, + "globby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "dev": true, + "requires": { + "array-union": "1.0.2", + "arrify": "1.0.1", + "glob": "7.1.2", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + }, + "dependencies": { + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + } + } + }, "globule": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/globule/-/globule-0.1.0.tgz", @@ -7128,11 +7773,23 @@ "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=", "dev": true }, + "ignore": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.5.tgz", + "integrity": "sha512-Q2daVnMtQJPacGrcCRyOEiI+syPCt+mR4YotoC0KEYeinV/6HztT5mUuVEj7UYyoNZ1jGYiu2XEem7I8oM44bg==", + "dev": true + }, "immutable": { "version": "3.7.6", "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.7.6.tgz", "integrity": "sha1-E7TTyxK++hVIKib+Gy665kAHHks=" }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, "in-publish": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz", @@ -7190,7 +7847,96 @@ "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", "dev": true, "requires": { - "amdefine": "1.0.1" + "amdefine": "1.0.1" + } + } + } + }, + "inquirer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-5.2.0.tgz", + "integrity": "sha512-E9BmnJbAKLPGonz0HeWHtbKf+EeSP93paWO3ZYoUpq/aowXvYGjjCSuashhXPpzbArIjBbji39THkxTz9ZeEUQ==", + "dev": true, + "requires": { + "ansi-escapes": "3.1.0", + "chalk": "2.4.1", + "cli-cursor": "2.1.0", + "cli-width": "2.2.0", + "external-editor": "2.2.0", + "figures": "2.0.0", + "lodash": "4.17.5", + "mute-stream": "0.0.7", + "run-async": "2.3.0", + "rxjs": "5.5.11", + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "through": "2.3.8" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.4.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "3.0.0" } } } @@ -7640,6 +8386,30 @@ } } }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", + "dev": true, + "requires": { + "is-path-inside": "1.0.1" + } + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "requires": { + "path-is-inside": "1.0.2" + } + }, "is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", @@ -7704,6 +8474,12 @@ "is-unc-path": "1.0.0" } }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true + }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", @@ -8162,6 +8938,12 @@ } } }, + "jest-docblock": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-21.2.0.tgz", + "integrity": "sha512-5IZ7sY9dBAYSV+YjQ0Ovb540Ku7AO9Z5o2Cg789xj167iQuZ2cG+z0f3Uct6WeYLbU6aQiM2pCs7sZ+4dotydw==", + "dev": true + }, "jquery": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.2.1.tgz", @@ -8246,6 +9028,12 @@ "jsonify": "0.0.0" } }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -8310,6 +9098,15 @@ } } }, + "jsx-ast-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.0.1.tgz", + "integrity": "sha1-6AGxs5mF4g//yHtA43SAgOLcrH8=", + "dev": true, + "requires": { + "array-includes": "3.0.3" + } + }, "karma": { "version": "0.13.22", "resolved": "https://registry.npmjs.org/karma/-/karma-0.13.22.tgz", @@ -8732,6 +9529,57 @@ "strip-bom": "2.0.0" } }, + "loader-fs-cache": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/loader-fs-cache/-/loader-fs-cache-1.0.1.tgz", + "integrity": "sha1-VuC/CL2XCLJqdltoUJhAyN7J/bw=", + "dev": true, + "requires": { + "find-cache-dir": "0.1.1", + "mkdirp": "0.5.1" + }, + "dependencies": { + "find-cache-dir": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-0.1.1.tgz", + "integrity": "sha1-yN765XyKUqinhPnjHFfHQumToLk=", + "dev": true, + "requires": { + "commondir": "1.0.1", + "mkdirp": "0.5.1", + "pkg-dir": "1.0.0" + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "2.0.1" + } + }, + "pkg-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", + "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", + "dev": true, + "requires": { + "find-up": "1.1.2" + } + } + } + }, "loader-runner": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.3.0.tgz", @@ -9818,6 +10666,12 @@ "integrity": "sha1-ARrM4ffL2H97prMJPWzZOSvhxXQ=", "dev": true }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, "nearley": { "version": "2.15.0", "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.15.0.tgz", @@ -9849,6 +10703,12 @@ "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", "dev": true }, + "nice-try": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.4.tgz", + "integrity": "sha512-2NpiFHqC87y/zFke0fC0spBXL3bBsoh/p5H1EFhshxjCR5+0g2d6BiXbUFz9v1sAcxsk2htp2eQnNIci2dIYcA==", + "dev": true + }, "node-fetch": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", @@ -10373,6 +11233,12 @@ } } }, + "object-hash": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.3.0.tgz", + "integrity": "sha512-05KzQ70lSeGSrZJQXE5wNDiTkBJDlUT/myi6RX9dVIvz7a7Qh4oH93BQdiPMn27nldYvVQCKMUaM83AfizZlsQ==", + "dev": true + }, "object-inspect": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.4.1.tgz", @@ -10547,6 +11413,15 @@ "wrappy": "1.0.2" } }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "1.2.0" + } + }, "opener": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/opener/-/opener-1.4.3.tgz", @@ -10863,6 +11738,12 @@ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", @@ -11117,6 +11998,12 @@ } } }, + "pluralize": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", + "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", + "dev": true + }, "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", @@ -11182,6 +12069,12 @@ "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", "dev": true }, + "prettier": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.14.2.tgz", + "integrity": "sha512-McHPg0n1pIke+A/4VcaS2en+pTNjy4xF+Uuq86u/5dyDO59/TtFZtQ708QIRkEZ3qwKz3GVkVa6mpxK/CpB8Rg==", + "dev": true + }, "pretty-hrtime": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", @@ -11704,6 +12597,12 @@ "define-properties": "1.1.2" } }, + "regexpp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.0.tgz", + "integrity": "sha512-g2FAVtR8Uh8GO1Nv5wpxW7VFVwHcCEr4wyA8/MHiRkO8uHoR5ntAA8Uq3P1vvMTX/BeQiRVSpDGLd+Wn5HNOTA==", + "dev": true + }, "regexpu-core": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz", @@ -11834,6 +12733,16 @@ "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", "dev": true }, + "require-uncached": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "dev": true, + "requires": { + "caller-path": "0.1.0", + "resolve-from": "1.0.1" + } + }, "requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -11858,12 +12767,28 @@ "global-modules": "1.0.0" } }, + "resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "dev": true + }, "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", "dev": true }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "2.0.1", + "signal-exit": "3.0.2" + } + }, "ret": { "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", @@ -11939,11 +12864,29 @@ "nearley": "2.15.0" } }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "requires": { + "is-promise": "2.1.0" + } + }, "rw": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/rw/-/rw-0.1.4.tgz", "integrity": "sha1-SQPL2AJIrg7eaFv1j9I2p6mymj4=" }, + "rxjs": { + "version": "5.5.11", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.11.tgz", + "integrity": "sha512-3bjO7UwWfA2CV7lmwYMBzj4fQ6Cq+ftHc2MvUe+WMS7wcdJ1LosDWmdjPQanYp2dBRj572p7PeU81JUxHKOcBA==", + "dev": true, + "requires": { + "symbol-observable": "1.0.1" + } + }, "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", @@ -12353,6 +13296,23 @@ "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=" }, + "slice-ansi": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", + "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + } + } + }, "sliced": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", @@ -13245,6 +14205,12 @@ "get-stdin": "4.0.1" } }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, "subarg": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", @@ -13310,6 +14276,12 @@ "upper-case": "1.1.3" } }, + "symbol-observable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", + "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=", + "dev": true + }, "symbol.prototype.description": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/symbol.prototype.description/-/symbol.prototype.description-1.0.0.tgz", @@ -13334,6 +14306,88 @@ "acorn-node": "1.3.0" } }, + "table": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/table/-/table-4.0.3.tgz", + "integrity": "sha512-S7rnFITmBH1EnyKcvxBh1LjYeQMmnZtCXSEbHcH6S0NoKit24ZuFO/T1vDcLdYsLQkM188PVVhQmzKIuThNkKg==", + "dev": true, + "requires": { + "ajv": "6.2.0", + "ajv-keywords": "3.1.0", + "chalk": "2.4.1", + "lodash": "4.17.5", + "slice-ansi": "1.0.0", + "string-width": "2.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.4.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + } + } + }, "tapable": { "version": "0.2.8", "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.2.8.tgz", @@ -13810,6 +14864,23 @@ "upper-case": "1.1.3" } }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "2.1.1" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + } + } + }, "urijs": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.17.0.tgz", @@ -15490,6 +16561,15 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "dev": true, + "requires": { + "mkdirp": "0.5.1" + } + }, "ws": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.5.tgz", diff --git a/package.json b/package.json index c111a04362..f54c93cd59 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,9 @@ "build-sass": "cd fec && gulp build-sass", "build-js": "cd fec && webpack", "build-production": "cd fec && webpack -p && gulp build-sass", + "format": "prettier --write ' ./fec/fec/static/js/**/*.js'", + "lint": "eslint ./fec/fec/static/js/**/*.js", + "format-lint": "npm run format && npm run lint", "test": "karma start", "test-single": "karma start --single-run --browsers PhantomJS", "watch": "nswatch" @@ -63,6 +66,13 @@ "clean-webpack-plugin": "^0.1.16", "enzyme": "^3.3.0", "enzyme-adapter-react-15.4": "^1.0.6", + "eslint": "^5.3.0", + "eslint-config-prettier": "^2.9.0", + "eslint-loader": "^2.1.0", + "eslint-plugin-import": "^2.13.0", + "eslint-plugin-no-unsanitized": "^3.0.2", + "eslint-plugin-prettier": "^2.6.2", + "eslint-plugin-react": "^7.10.0", "fs": "0.0.1-security", "gulp": "^3.9.0", "gulp-clean": "^0.3.2", @@ -95,6 +105,7 @@ "mockdate": "^2.0.2", "nswatch": "0.2.0", "phantomjs": "^2.1.7", + "prettier": "^1.14.2", "react-addons-test-utils": "^15.6.2", "scrollmonitor": "^1.2.3", "sinon": "^1.17.2", From 4a3fd2766f2039b87c1ef44ad4f34cf0767fee8e Mon Sep 17 00:00:00 2001 From: Andrew Burnes <andrew.burnes@gsa.gov> Date: Thu, 9 Aug 2018 17:35:57 -0300 Subject: [PATCH 02/49] Remove filter remove_digits from section titles and anchors --- fec/home/templates/blocks/section.html | 2 +- fec/home/templates/partials/section-nav.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fec/home/templates/blocks/section.html b/fec/home/templates/blocks/section.html index b83581da18..96482961dc 100644 --- a/fec/home/templates/blocks/section.html +++ b/fec/home/templates/blocks/section.html @@ -1,7 +1,7 @@ {% load wagtailcore_tags %} {% load filters %} -<section id="{% filter remove_digits|slugify %}{{ value.title }}{% endfilter %}" class="option {% if value.hide_title %}option--no-top-border{% endif %}"> +<section id="{{ value.title | slugify}}" class="option {% if value.hide_title %}option--no-top-border{% endif %}"> {% if not value.hide_title %}<h2>{{ value.title }}</h2>{% endif %} {% if value.aside %} <div class="option__content"> diff --git a/fec/home/templates/partials/section-nav.html b/fec/home/templates/partials/section-nav.html index 90612c7973..98ecba19d9 100644 --- a/fec/home/templates/partials/section-nav.html +++ b/fec/home/templates/partials/section-nav.html @@ -6,7 +6,7 @@ <ul class="sidebar__content"> {% for section in sections %} <li class="side-nav__item"> - <a class="side-nav__link" href="#{% filter remove_digits|slugify %}{{ section.value.title }}{% endfilter %}">{{ section.value.title }}</a> + <a class="side-nav__link" href="#{{ section.value.title | slugify }}">{{ section.value.title }}</a> </li> {% endfor %} {% if citations %} From 734c864156dcaa3711c5b92787d7933b20d54b38 Mon Sep 17 00:00:00 2001 From: Laura Beaufort <31420082+lbeaufort@users.noreply.github.com> Date: Fri, 10 Aug 2018 09:16:49 -0400 Subject: [PATCH 03/49] Remove 'Last general election' and 'General election' from election profile pages --- fec/fec/static/js/modules/election-utils.js | 30 ------------------- fec/fec/static/js/pages/elections.js | 1 - fec/fec/static/js/templates/electionDates.hbs | 12 -------- 3 files changed, 43 deletions(-) delete mode 100644 fec/fec/static/js/templates/electionDates.hbs diff --git a/fec/fec/static/js/modules/election-utils.js b/fec/fec/static/js/modules/election-utils.js index ebbe45cf04..576f2793be 100644 --- a/fec/fec/static/js/modules/election-utils.js +++ b/fec/fec/static/js/modules/election-utils.js @@ -10,7 +10,6 @@ var topojson = require('topojson'); var sprintf = require('sprintf-js').sprintf -var electionDatesTemplate = require('../templates/electionDates.hbs'); var electionOfficesTemplate = require('../templates/electionOffices.hbs'); var districts = require('../data/districts.json'); @@ -63,34 +62,6 @@ function findDistricts(districts) { }); } -function getElections(state, office, cycle) { - var officeSymbol = { - 'house': 'H', - 'senate': 'S', - 'president': 'P' - }; - - var defaultQuery = { - 'sort': '-election_date', - 'per_page': 2, - 'election_type_id': 'G', - 'election_state': state, - 'office_sought': officeSymbol[office] - }; - - var query = office !== 'president' ? defaultQuery : _.omit(defaultQuery, 'election_state'); - - var url = helpers.buildUrl(['election-dates'], query); - var $election_dates_results = $('.election-dates'); - $.getJSON(url).done(function(response) { - var results = { - last: moment(response.results[1].election_date).format('MMMM DD, YYYY'), - current: moment(response.results[0].election_date).format('MMMM DD, YYYY') - }; - $election_dates_results.html(electionDatesTemplate(results)); - }); -} - function getStateElectionOffices(state) { var query = { state: state @@ -110,6 +81,5 @@ module.exports = { encodeDistrict: encodeDistrict, findDistrict: findDistrict, findDistricts: findDistricts, - getElections: getElections, getStateElectionOffices: getStateElectionOffices }; diff --git a/fec/fec/static/js/pages/elections.js b/fec/fec/static/js/pages/elections.js index c6379d228c..4750548723 100644 --- a/fec/fec/static/js/pages/elections.js +++ b/fec/fec/static/js/pages/elections.js @@ -71,7 +71,6 @@ $(document).ready(function() { }); electionUtils.getStateElectionOffices(context.election.state); - electionUtils.getElections(context.election.state, context.election.office, context.election.cycle); tables.initSpendingTables('.data-table', context, spendingTableOpts); new ElectionForm('#election-nav'); diff --git a/fec/fec/static/js/templates/electionDates.hbs b/fec/fec/static/js/templates/electionDates.hbs deleted file mode 100644 index 2efcc31ef6..0000000000 --- a/fec/fec/static/js/templates/electionDates.hbs +++ /dev/null @@ -1,12 +0,0 @@ -<table class="t-sans usa-width-three-fourths"> - <tbody> - <tr> - <td class="figure__label">Last general election:</td> - <td class="figure__value">{{last}}</td> - </tr> - <tr> - <td class="figure__label">General election:</td> - <td class="figure__value">{{current}}</td> - </tr> - </tbody> -</table> \ No newline at end of file From faeec32a8ed12ef9be20c9f2bf1ec7e92558e5c0 Mon Sep 17 00:00:00 2001 From: Laura Beaufort <31420082+lbeaufort@users.noreply.github.com> Date: Fri, 10 Aug 2018 12:06:33 -0400 Subject: [PATCH 04/49] Re-enable Python tests --- .circleci/config.yml | 1 + fec/home/migrations/0001_initial.py | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index bf9d7a3798..a0c47771a4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -75,6 +75,7 @@ jobs: npm run build-sass cd fec DJANGO_SETTINGS_MODULE=fec.settings.production python manage.py collectstatic --noinput -v 0 + ./manage.py test npm run test-single - run: diff --git a/fec/home/migrations/0001_initial.py b/fec/home/migrations/0001_initial.py index bd16119f6f..f992c50a9b 100644 --- a/fec/home/migrations/0001_initial.py +++ b/fec/home/migrations/0001_initial.py @@ -3,13 +3,22 @@ from django.db import models, migrations + class Migration(migrations.Migration): + dependencies = [('wagtailcore', '0040_page_draft_title')] + operations = [ migrations.CreateModel( name='HomePage', fields=[ - ('page_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='wagtailcore.Page')), + ('page_ptr', + models.OneToOneField( + parent_link=True, + auto_created=True, + primary_key=True, + serialize=False, + to='wagtailcore.Page')), ], options={ 'abstract': False, From 5127c46981a57261a0ce263b4c433850f0b0c084 Mon Sep 17 00:00:00 2001 From: Laura Beaufort <31420082+lbeaufort@users.noreply.github.com> Date: Fri, 10 Aug 2018 14:17:26 -0400 Subject: [PATCH 05/49] Use FEC-wide license and contributing documents --- CONTRIBUTING.md | 111 +++--------------------------------------------- LICENSE.md | 75 +------------------------------- 2 files changed, 6 insertions(+), 180 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 32eb58f7dd..69a3f23073 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,111 +1,10 @@ ## Welcome -We're glad you're thinking about contributing to an 18F open source project! If you're unsure about anything, ask us — or submit the issue or pull request anyway. The worst that can happen is that we’ll politely ask you to change something. +We're glad you're thinking about contributing to an FEC open source project! If you're unsure about anything, ask us — or submit the issue or pull request anyway. The worst that can happen is that we’ll politely ask you to change something. -We love all friendly contributions, and we welcome your ideas about how to make the FEC's online presence more user friendly, accessible, and elegant. +We love all friendly contributions, and we welcome your ideas about how to make the FEC's online presence more user friendly, accessible and elegant. -To ensure a welcoming environment for our projects, our staff follows the [18F Code of Conduct](https://github.com/18F/code-of-conduct/blob/master/code-of-conduct.md); contributors should do the same. Please also check out the [18F Open Source Policy GitHub repository]( https://github.com/18f/open-source-policy). +To ensure a welcoming environment for our projects, our staff follows the FEC Code of Conduct [pending]; contributors should do the same. Please also check out the [FEC Open Source Policy](https://github.com/fecgov/FEC/blob/master/OPEN-SOURCE-POLICY.md). -If you’d prefer, you can also reach us by [email](mailto:betafeedback@fec.gov). +If you’d prefer, you can also reach us by [email](mailto:opensource@fec.gov). -## Public domain -All contributions to this project will be released under the CC0 dedication. By submitting a pull request, you are agreeing to comply with this waiver of copyright interest. - -### Issues -We use GitHub issues to track user issues and team tasks. Whenever possible, we follow this outline: - -1. Goal: a quick blurb explaining the bug or what the issue should accomplish. What is the user need? -2. Completion criteria: how we’ll know that this issue has been completed -3. Tasks to completion: - - [ ] Use - - [ ] Markdown - - [ ] Checklists -4. Dependencies (optional): What other issues out there need to be completed before you can do this one? Include links to tickets with the dependency. - -## Pull request and branching guidance - -### Pull requests - -#### Writing a pull request -Authoring a good pull request saves time for both the developer and anyone reviewing changes in code. It re-enforces the purpose and validity of the code change and it also makes it easier for reviewers to understand the changes at a glance before diving in. - -**Do:** - - * Use a descriptive but succinct title - * Try to keep it under 75 characters - * Use a _verb/adjective_ aimed at a particular **feature**: - * “_New_ **meetings archive** page,” - * “_Changes_ to **candidate page** design,” - * “_Fix_ for **election page** button” - * Follow the pre-populated fields in the pull request template. Remember to delete fields you don’t use. - * Include a summary that describes components that will be affected in the application - * Include issue number(s) and other related PRs - * Add screenshots, short videos, or gifs - * When there are changes to styling or to the interface, a before/after image is helpful - * In interactive features, an animated gif captures the movement and activity - * Check to remove `print` statements and commented out code - * Lint your code and double check indents and make sure to use proper spacing - -**Don’t:** - - * Use overly verbose or technically detailed PR titles. - * “New class .foo-bar on profile sections plus javascript changes on design pages,” could be “New interactions and designs for Committee profiles,” - * “Tooltips on calendar now works when hovered with points” could be “Fixes for Calendar tooltips” - * Leave unused blank fields in the pull request template when you submit it - - -##### Resources - -* An [article about writing effective pull requests](https://www.atlassian.com/blog/git/written-unwritten-guide-pull-requests) from Atlassian. -* An [article on writing good commit messages](https://chris.beams.io/posts/git-commit/) by Chris Beams - - -### Branching - -#### Feature development branching -If you are developing a new feature, you can make a branch `feature/name-of-new-feature` but you can also use git flow to start it using `git flow feature start name-of-new-feature`. - -#### Quick fix branching -Branches for fixes to an existing feature or modifications to the general codebase that are not critical (in other words, can wait until the next scheduled release) can be established as a feature with a name starting with “fix-” to differentiate themselves from full fledged features, for example `feature/fix-broken-button`. - -#### Work in progress (WIP) -Work in progress PRs may be helpful to show that a feature is in active development for an extended period of time, especially if you're collaborating with other folks on an issue. Generally speaking, work shouldn't sit unmerged for long periods of time (that is a development antipattern), but in some cases this is preferred as it helps facilitate communication between team members and provides visibility into ongoing work. Furthermore, it will allow the tests to run in CircleCI, which provides you with feedback as you work. You should be pushing your commits regularly as you make progress so that you receive automated testing feedback early and often to take advantage of this! - -If you are intentially opening a PR early and intend it to be a work in progress, please do the following: - -- Put `[WIP]` at the start of the PR title -- Place `WIP: PLEASE DO NOT MERGE` in bold at the top of the PR description - -When the PR is ready for full review and no longer a WIP, please remove these two items. - -### Committing work in a branch -When you begin to work in a branch, you'll want to follow these guidelines for the commits you will make before you push them remotely: - -- Good commit messages start with a verb, like “adds” or “changes” or “removes.” -- Be sure to talk about the nature of the change you're making. Explain why the change is needed, rather than simply describing the bug or task it addresses. -- If your commit resolves an issue, reference it in the commit messages. For example “fixes #555.” Read more GitHub guidance on [closing issues via commit messages](https://help.github.com/articles/closing-issues-via-commit-messages/). -- We encourage you to follow the [50/72 format](http://stackoverflow.com/questions/2290016/git-commit-messages-50-72-formatting). - -### Ready for review - -#### Labeling -After you open the PRs and it’s ready for review, apply the label “plz-review” and tag a specific person to review it. If the PRs needs to be included in the next release, apply the “Before release” label so that reviewers recognize that it’s a priority. - -#### Getting a PR review -Sometimes tagging a reviewer may not be enough. If a PR needs to be reviewed in a timely manner, reach out to folks individually to ask for help as they may not know the urgency of the PR needing a review. - -#### Reviewing a pull request -Reviewing pull requests is not always easy and reviewers should make sure to block out time in their schedule so they are not rushed. Always make sure to pull down the branch and test the work on your local machine. DO NOT BLINDLY MERGE IN PRS YOU DO NOT UNDERSTAND. Do not feel shy about asking questions and requesting the author of the pull request to explain certain sections of their code. That is the purpose of a code review. - -##### Resources -* [Tips for code reviews](http://engineering.khanacademy.org/posts/tips-for-code-reviews.htm) from Khan Academy - -### Urgent fixes between releases - -#### Hotfixes -To reiterate the [onboarding documentation](https://docs.google.com/document/d/18ZjPvNrdW3wn9pUx7hSNbaMlaEaPPOGzOHh-WjLiVOg/edit#heading=h.8a6bhbb6fbdz):<br> -<br> -_“A hotfix is intended to address an issue that is having a critical impact to the production site that must be remediated outside of the normal release schedule. These should very much be the exception, not the rule, but they do come up from time to time and can range from a glaring typo to something that brings down core functionality (e.g., an API endpoint failing).”_<br> -<br> -Hotfixes should only address urgent issues that are determined by project managers to be a critical detriment to production. Otherwise, fixes and updates can be rolled into the next release. If there is a large number of hotfixes needed, the team should consider making an extra mid-sprint mini release instead of deploying several hotfixes.<br> -<br> -A detailed step-by-step guide to creating hotfixes can be found in the [onboarding documentation](https://docs.google.com/document/d/18ZjPvNrdW3wn9pUx7hSNbaMlaEaPPOGzOHh-WjLiVOg/edit#heading=h.8a6bhbb6fbdz). +For our full contributing guidelines, please see our [Default Contributor License Agreement](https://github.com/fecgov/FEC/blob/master/CONTRIBUTING.md). diff --git a/LICENSE.md b/LICENSE.md index bea7617bb0..08fff12265 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -5,77 +5,4 @@ This project includes a mix of the following: * Open source works that are not in the public domain * Open source work by the U.S. government that is in the public domain -## Parts of this project that are not in the public domain - -### Files licensed under the SIL Open Font License - -The Karla font files in `fec-cms/fec/fec/static/fonts/` are from [Google Web Fonts](https://fonts.google.com/specimen/Karla), licensed under the [SIL Open Font License](http://scripts.sil.org/cms/scripts/page.php?item_id=OFL), and copyright [Jonny Pinhorn](https://github.com/jonpinhorn) with Reserved Font Name ‘Karla'. - - -### Files licensed under another license - -The Gandhi Serif font files in `fec-cms/fec/fec/static/fonts/` are from [Librerias Gandhi](http://www.tipografiagandhi.com/), and copyright 2012 [Librerias Gandhi S.A. de C.V.](http://www.gandhi.com.mx/) with Reserved Font Name ‘Gandhi Serif’. The English version of this License is reproduced below. - -#### Full English version of the license text for Gandhi Serif: - -``` -Conditions of use - -You may: -- Install the fonts on as many devices as you wish. -- Distribute the fonts to anyone you wish. -- Use the fonts in any commercial or non-commercial document. -- Save the fonts in a format that would best fit your purposes. - -You may not: -- Modify the fonts in a font editor software. -- Sell or rent out the fonts. - -Notes: -- The fonts and any other accompanying written or electronic materials are provided "as is" without warranty of any kind, expressed or implied. The authors do not warrant that the functions contained in the fonts will meet the user's requirements. -- The authors shall not be liable for any direct, indirect, consequential, or incidental damages (including damages from loss of business profits, business interruption, loss of business information, and the like) arising out of the use of or inability to use the fonts. -``` -## Image licenses - -This site also includes a number of images that have various copyright statuses, including Creative Commons licenses and public domain. These images are in the `fec-cms/fec/fec/static/img/` directory. - -## The rest of this project is in the worldwide public domain - -As a work of the United States government, this project is in the public domain within the United States. - -Additionally, we waive copyright and related rights in the work worldwide through the [CC0 1.0 Universal public domain dedication](https://creativecommons.org/publicdomain/zero/1.0/). - -### CC0 1.0 universal summary -What follows is a human-readable summary of the Legal Code. [Read the full text](https://creativecommons.org/publicdomain/zero/1.0/legalcode). - -#### No copyright -The person who associated a work with this deed has dedicated the work to the public domain by waiving all of his or her rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law. - -You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission. - -### FEC data exceptions - -A few restrictions limit the way you can use FEC data. For example, you can’t use contributor lists for commercial purposes or to solicit donations. [Learn more on FEC.gov.](https://transition.fec.gov/pages/brochures/saleuse.shtml) - -#### Other information -In no way are the patent or trademark rights of any person affected by CC0, nor are the rights that other persons may have in the work or in how the work is used, such as publicity or privacy rights. - -Unless expressly stated otherwise, the person who associated a work with this deed makes no warranties about the work, and disclaims liability for all uses of the work, to the fullest extent permitted by applicable law. When using or citing the work, you should not imply endorsement by the author or the affirmer. - -## Reuse of open-source style guides -Much of the design, guidance, and code in the FEC style guide is based on user interface (UI) patterns from the [U.S. Web Design Standards](https://standards.usa.gov/), including: -- Grid column classes: [/scss/_grid.scss](/blob/master/scss/_grid.scss) -- Some colors in the color palette: [/scss/_variables.scss](/blob/master/scss/_variables.scss) -- Header navigation with mega menu -- Footer navigation and guidance -- Side navigation - -### Contributions to this project - -As stated in [CONTRIBUTING](CONTRIBUTING.md), all contributions to this project will be released under the CC0 dedication. By submitting a pull request, you are agreeing to comply with this waiver of copyright interest. - - - - - - +Please see our [Default License](https://github.com/fecgov/FEC/blob/master/LICENSE.md). From f6b344bc03fe73198da3a870e74095a9750ad678 Mon Sep 17 00:00:00 2001 From: Laura Beaufort <31420082+lbeaufort@users.noreply.github.com> Date: Fri, 10 Aug 2018 09:16:49 -0400 Subject: [PATCH 06/49] Remove 'Last general election' and 'General election' from election profile pages --- fec/fec/static/js/modules/election-utils.js | 30 ------------------- fec/fec/static/js/pages/elections.js | 1 - fec/fec/static/js/templates/electionDates.hbs | 12 -------- 3 files changed, 43 deletions(-) delete mode 100644 fec/fec/static/js/templates/electionDates.hbs diff --git a/fec/fec/static/js/modules/election-utils.js b/fec/fec/static/js/modules/election-utils.js index ebbe45cf04..576f2793be 100644 --- a/fec/fec/static/js/modules/election-utils.js +++ b/fec/fec/static/js/modules/election-utils.js @@ -10,7 +10,6 @@ var topojson = require('topojson'); var sprintf = require('sprintf-js').sprintf -var electionDatesTemplate = require('../templates/electionDates.hbs'); var electionOfficesTemplate = require('../templates/electionOffices.hbs'); var districts = require('../data/districts.json'); @@ -63,34 +62,6 @@ function findDistricts(districts) { }); } -function getElections(state, office, cycle) { - var officeSymbol = { - 'house': 'H', - 'senate': 'S', - 'president': 'P' - }; - - var defaultQuery = { - 'sort': '-election_date', - 'per_page': 2, - 'election_type_id': 'G', - 'election_state': state, - 'office_sought': officeSymbol[office] - }; - - var query = office !== 'president' ? defaultQuery : _.omit(defaultQuery, 'election_state'); - - var url = helpers.buildUrl(['election-dates'], query); - var $election_dates_results = $('.election-dates'); - $.getJSON(url).done(function(response) { - var results = { - last: moment(response.results[1].election_date).format('MMMM DD, YYYY'), - current: moment(response.results[0].election_date).format('MMMM DD, YYYY') - }; - $election_dates_results.html(electionDatesTemplate(results)); - }); -} - function getStateElectionOffices(state) { var query = { state: state @@ -110,6 +81,5 @@ module.exports = { encodeDistrict: encodeDistrict, findDistrict: findDistrict, findDistricts: findDistricts, - getElections: getElections, getStateElectionOffices: getStateElectionOffices }; diff --git a/fec/fec/static/js/pages/elections.js b/fec/fec/static/js/pages/elections.js index c6379d228c..4750548723 100644 --- a/fec/fec/static/js/pages/elections.js +++ b/fec/fec/static/js/pages/elections.js @@ -71,7 +71,6 @@ $(document).ready(function() { }); electionUtils.getStateElectionOffices(context.election.state); - electionUtils.getElections(context.election.state, context.election.office, context.election.cycle); tables.initSpendingTables('.data-table', context, spendingTableOpts); new ElectionForm('#election-nav'); diff --git a/fec/fec/static/js/templates/electionDates.hbs b/fec/fec/static/js/templates/electionDates.hbs deleted file mode 100644 index 2efcc31ef6..0000000000 --- a/fec/fec/static/js/templates/electionDates.hbs +++ /dev/null @@ -1,12 +0,0 @@ -<table class="t-sans usa-width-three-fourths"> - <tbody> - <tr> - <td class="figure__label">Last general election:</td> - <td class="figure__value">{{last}}</td> - </tr> - <tr> - <td class="figure__label">General election:</td> - <td class="figure__value">{{current}}</td> - </tr> - </tbody> -</table> \ No newline at end of file From f06313ec6b3cc56f5a182e03e4b4c56f83359150 Mon Sep 17 00:00:00 2001 From: Laura Beaufort <31420082+lbeaufort@users.noreply.github.com> Date: Mon, 6 Aug 2018 09:12:48 -0400 Subject: [PATCH 07/49] Revert "Set max cycle to 2018 for elections two year period dropdown" This reverts commit 672c046fa81dce4bf1108a0d59c3980c67ba02ea. --- fec/data/templates/macros/cycle-select.jinja | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/fec/data/templates/macros/cycle-select.jinja b/fec/data/templates/macros/cycle-select.jinja index 33d5665561..e281c0ed7c 100644 --- a/fec/data/templates/macros/cycle-select.jinja +++ b/fec/data/templates/macros/cycle-select.jinja @@ -65,13 +65,11 @@ <div class="cycle-select"> <label for="{{id}}-cycle" class="label cycle-select__label">Two-year period</label> <select id="{{id}}-cycle" class="js-cycle" name="cycle" data-cycle-location="path"> - {% for cycle_value in cycles | sort(reverse=True) %} - {% if cycle_value <= 2018 %} - <option - value="{{ cycle_value }}" - {% if cycle and cycle <= cycle_value and cycle > (cycle_value - 2) %}selected{% endif %} - >{{ cycle_value|fmt_year_range }}</option> - {% endif %} + {% for each in cycles | sort(reverse=True) %} + <option + value="{{ each }}" + {% if cycle and cycle <= each and cycle > (each - 2) %}selected{% endif %} + >{{ each|fmt_year_range }}</option> {% endfor %} </select> </div> From 8af3f92427693e4e750462f680522e25eda58e30 Mon Sep 17 00:00:00 2001 From: Laura Beaufort <31420082+lbeaufort@users.noreply.github.com> Date: Tue, 7 Aug 2018 10:44:08 -0400 Subject: [PATCH 08/49] Remove unused templates --- .../elections/candidate-comparison-tab.jinja | 32 -------- .../elections/detailed-comparison-tab.jinja | 76 ------------------- 2 files changed, 108 deletions(-) delete mode 100644 fec/data/templates/partials/elections/candidate-comparison-tab.jinja delete mode 100644 fec/data/templates/partials/elections/detailed-comparison-tab.jinja diff --git a/fec/data/templates/partials/elections/candidate-comparison-tab.jinja b/fec/data/templates/partials/elections/candidate-comparison-tab.jinja deleted file mode 100644 index 03c70e36c2..0000000000 --- a/fec/data/templates/partials/elections/candidate-comparison-tab.jinja +++ /dev/null @@ -1,32 +0,0 @@ -{% import 'macros/cycle-select.jinja' as select %} - -<section id="section-1" role="tabpanel" aria-hidden="true" aria-labelledby="section-1-heading"> - <div class="container"> - <h2 id="section-1-heading">Compare candidate financial totals</h2> - <p>This table only shows candidates who have registered <em>and</em> filed a financial report. Looking for <a href="/data/candidates/?office={{ office_code|upper }}&cycle={{ cycle }}&district={{ district }}&state={{ state|upper }}">all candidates who registered</a>?</p> - <div class="slab slab--inline slab--neutral u-padding--left u-padding--right"> - {{ select.election_cycle_select(cycles, cycle, 'summary')}} - <div class="entity__figure row"> - <div class="heading--section heading--with-action u-no-margin"> - <h3 class="heading__left">Candidates in this election</h3> - <div class="heading__right"> - <span class="is-incumbent t-sans t-note">Incumbent</span> - </div> - </div> - <table id="results" class="data-table data-table--heading-borders scrollX" data-type="candidates"> - <thead> - <th role="col">Candidate</th> - <th role="col">Party</th> - <th role="col">Total receipts</th> - <th role="col">Total disbursements</th> - <th role="col">Cash on hand</th> - <th role="col">Source reports</th> - </thead> - </table> - <div class="datatable__note"> - <span class="t-note">Totals reflect the sum reported by all of a candidate's authorized committees.</span> - </div> - </div> - </div> - </div> -</section> diff --git a/fec/data/templates/partials/elections/detailed-comparison-tab.jinja b/fec/data/templates/partials/elections/detailed-comparison-tab.jinja deleted file mode 100644 index c9b4a82a78..0000000000 --- a/fec/data/templates/partials/elections/detailed-comparison-tab.jinja +++ /dev/null @@ -1,76 +0,0 @@ -{% import 'macros/cycle-select.jinja' as select %} - -<section id="section-2" role="tabpanel" aria-hidden="true" aria-labelledby="section-2-heading"> - <div class="container"> - <h2 id="section-2-heading">Compare individual contributions to candidates</h2> - <div class="slab slab--inline slab--neutral u-padding--left u-padding--right"> - {{ select.election_cycle_select(cycles, cycle, 'details')}} - <div id="comparison"></div> - <div class="entity__figure row"> - <div class="heading--section heading--with-action"> - <h3 class="heading__left">Individual contributions</h3> - </div> - <div class="row"> - <fieldset class="toggles js-toggles"> - <legend class="label">Group by:</legend> - <label for="toggle-size"> - <input id="toggle-size" type="radio" class="js-panel-toggle-control" name="election-aggregate" value="by-size" checked> - <span class="button--alt">Contribution size</span> - </label> - <label for="toggle-state"> - <input id="toggle-state" type="radio" class="js-panel-toggle-control" name="election-aggregate" value="by-state"> - <span class="button--alt">Contributor state</span> - </label> - </fieldset> - </div> - - <div class="row"> - <div id="by-size" class="panel-toggle-element" aria-hidden="false"> - <div class="results-info results-info--simple"></div> - <table class="data-table data-table--heading-borders scrollX" data-type="by-size"> - <thead> - <th scope="col">Candidate</th> - <th scope="col">$200 and under</th> - <th scope="col">$200.01—$499.99</th> - <th scope="col">$500—$999.99</th> - <th scope="col">$1,000—$1,999.99</th> - <th scope="col">$2,000 and over</th> - </thead> - </table> - </div> - </div> - - <div class="row"> - <div id="by-state" class="panel-toggle-element" aria-hidden="true"> - <div class="results-info results-info--simple"> - <div class="js-toggles toggles toggles--small map-toggles" aria-label="Toggle view"> - <label for="toggle-table"> - <input id="toggle-table" type="radio" class="js-panel-toggle-control" name="state-view" value="state-table" checked> - <span class="button--alt button--small button--table">Table</span> - </label> - <label for="toggle-map"> - <input id="toggle-map" type="radio" class="js-panel-toggle-control" name="state-view" value="state-maps"> - <span class="button--alt button--small button--map">Map</span> - </label> - </div> - </div> - <div id="state-table"> - <table class="data-table data-table--heading-borders scrollX panel-toggle-element" data-type="by-state" aria-hidden="false"> - <thead><tr></tr></thead> - </table> - </div> - <div id="state-maps" class="panel-toggle-element" aria-hidden="true"> - <div class="choropleths row"></div> - <div class="row"> - <div class="legend-container"> - <span class="t-block">By state: total amount received</span> - <svg></svg> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> -</section> From 5b5e0891914cb1f5b9dacb15a11934fa07e52f7e Mon Sep 17 00:00:00 2001 From: Laura Beaufort <31420082+lbeaufort@users.noreply.github.com> Date: Tue, 7 Aug 2018 10:45:13 -0400 Subject: [PATCH 09/49] Update election search breadcrumbs --- fec/data/templates/elections.jinja | 2 +- fec/data/templates/macros/page-header.jinja | 13 +++++++++++++ fec/data/utils.py | 1 + 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/fec/data/templates/elections.jinja b/fec/data/templates/elections.jinja index 636250ac9b..629516e898 100644 --- a/fec/data/templates/elections.jinja +++ b/fec/data/templates/elections.jinja @@ -16,7 +16,7 @@ <link rel="stylesheet" type="text/css" href="{{ asset_for_css('elections.css') }}" /> {% endblock %} {% block body %} - {{ header.header(crumb) }} + {{ header.header(title, crumb) }} <div class="u-padding--left u-padding--right tab-interface"> <header class="main"> {% if office == 'president' %} diff --git a/fec/data/templates/macros/page-header.jinja b/fec/data/templates/macros/page-header.jinja index 40a519d91d..ba8e7a48a6 100644 --- a/fec/data/templates/macros/page-header.jinja +++ b/fec/data/templates/macros/page-header.jinja @@ -15,6 +15,19 @@ <span class="breadcrumbs__separator">›</span> Audit search </li> + {% elif 'Election United States' in title %} + <li class="breadcrumbs__item"> + <span class="breadcrumbs__separator">›</span> + <a href="/data" class="breadcrumbs__link">Campaign finance data</a> + </li> + <li class="breadcrumbs__item"> + <span class="breadcrumbs__separator">›</span> + <a href="/data/elections" class="breadcrumbs__link">Find candidates and elections by location</a> + </li> + <li class="breadcrumbs__item breadcrumbs__item--current"> + <span class="breadcrumbs__separator">›</span> + <span>{{ links }}</span> + </li> {% else %} {% if title != 'Campaign finance data' %} <li class="breadcrumbs__item"> diff --git a/fec/data/utils.py b/fec/data/utils.py index 5b1b7f6442..b6f296584e 100644 --- a/fec/data/utils.py +++ b/fec/data/utils.py @@ -115,6 +115,7 @@ def get_cycles(max_cycle=None): def election_title(cycle, office, state=None, district=None): + # If this logic changes, please update `page-header.jinja` logic base = ' '.join([str(cycle), 'Election', 'United States', office.capitalize()]) parts = [base] if state: From 47af371c4dfa5884fcfc1b993f240a39c7b408ef Mon Sep 17 00:00:00 2001 From: Laura Beaufort <31420082+lbeaufort@users.noreply.github.com> Date: Tue, 7 Aug 2018 10:46:41 -0400 Subject: [PATCH 10/49] Add future elections to election profile pages --- fec/data/templates/election-lookup.jinja | 2 +- fec/data/templates/elections.jinja | 1 + fec/data/templates/macros/cycle-select.jinja | 4 ++-- fec/data/views.py | 12 ++++++------ 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/fec/data/templates/election-lookup.jinja b/fec/data/templates/election-lookup.jinja index 3d0ea845f0..971cb4dc71 100644 --- a/fec/data/templates/election-lookup.jinja +++ b/fec/data/templates/election-lookup.jinja @@ -18,7 +18,7 @@ <div class="search-controls"> <form> <div class="search-controls__row"> - {{ select.cycle_select(cycles, location='form', range=True) }} + {{ select.cycle_select(cycles, cycle, location='form', id='cycle-1') }} </div> <div class="search-controls__row"> <div class="search-controls__either"> diff --git a/fec/data/templates/elections.jinja b/fec/data/templates/elections.jinja index 629516e898..eb237a4bd0 100644 --- a/fec/data/templates/elections.jinja +++ b/fec/data/templates/elections.jinja @@ -52,6 +52,7 @@ var context = { election: { cycle: '{{ cycle }}', + election_full: true, office: '{{ office }}', state: '{{ state or '' }}', stateFull: '{{ state_full or '' }}', diff --git a/fec/data/templates/macros/cycle-select.jinja b/fec/data/templates/macros/cycle-select.jinja index e281c0ed7c..9404c3001e 100644 --- a/fec/data/templates/macros/cycle-select.jinja +++ b/fec/data/templates/macros/cycle-select.jinja @@ -63,13 +63,13 @@ {% set cycle = cycle | int %} <div class="row content__section"> <div class="cycle-select"> - <label for="{{id}}-cycle" class="label cycle-select__label">Two-year period</label> + <label for="{{id}}-cycle" class="label cycle-select__label">Election</label> <select id="{{id}}-cycle" class="js-cycle" name="cycle" data-cycle-location="path"> {% for each in cycles | sort(reverse=True) %} <option value="{{ each }}" {% if cycle and cycle <= each and cycle > (each - 2) %}selected{% endif %} - >{{ each|fmt_year_range }}</option> + >{{ each }}</option> {% endfor %} </select> </div> diff --git a/fec/data/views.py b/fec/data/views.py index 7e3f9726fe..7cdf1b5474 100644 --- a/fec/data/views.py +++ b/fec/data/views.py @@ -377,21 +377,21 @@ def committee(request, committee_id): def elections_lookup(request): - cycles = utils.get_cycles(utils.current_cycle()) + + cycle = utils.current_cycle() + cycles = utils.get_cycles(cycle + 4) return render(request, 'election-lookup.jinja', { 'parent': 'data', - 'cycles': cycles + 'cycles': cycles, + 'cycle': cycle, }) def elections(request, office, cycle, state=None, district=None): cycle = int(cycle) - # Get all cycles up until the cycle from the URL if it's beyond the current cycle - # this fixes the issue of an election page not showing user-provided cycle - # in the cycle select - max_cycle = cycle if cycle > utils.current_cycle() else utils.current_cycle() + max_cycle = utils.current_cycle() + 4 cycles = utils.get_cycles(max_cycle) if office.lower() == 'president': From d3b3a1bcd39cde36915d18e74fe622f746cd11c1 Mon Sep 17 00:00:00 2001 From: john carroll <jcarroll@fec.gov> Date: Fri, 10 Aug 2018 16:25:44 -0400 Subject: [PATCH 11/49] remove unused contact.html and fix contact-page.html template --- fec/home/templates/home/contact.html | 121 ---------------------- fec/home/templates/home/contact_page.html | 2 +- 2 files changed, 1 insertion(+), 122 deletions(-) delete mode 100644 fec/home/templates/home/contact.html diff --git a/fec/home/templates/home/contact.html b/fec/home/templates/home/contact.html deleted file mode 100644 index 456617b83d..0000000000 --- a/fec/home/templates/home/contact.html +++ /dev/null @@ -1,121 +0,0 @@ -{% extends "base.html" %} -{% load wagtailcore_tags %} -{% load staticfiles %} -{% block body_class %}template-{{ self.get_verbose_name | slugify }}{% endblock %} - -{% block content %} -{% include 'partials/breadcrumbs.html' with page=self style='secondary' %} - -<div class="hero__banner--office"></div> -<article class="main"> - <div class="container"> - <header class="heading--main"> - <h1>{{ self.title }}</h1> - </header> - <div class="main__content--full u-padding--top"> - <div class="usa-width-one-third"> - <h2>General inquiries</h2> - <ul> - <li class="contact-item contact-item--phone"> - <div class="contact-item__content"> - <h5 class="contact-item__title">Toll free</h5> - <span class="t-block">1-800-424-9530</span> - <span class="t-block">8:30 a.m. to 5:30 p.m. Eastern Time</span> - </div> - </li> - <li class="contact-item"> - <div class="contact-item__content--offset"> - <h5 class="contact-item__title">Local</h5> - <span class="t-block">202-694-1000</span> - </div> - </li> - <li class="contact-item"> - <div class="contact-item__content--offset"> - <h5 class="contact-item__title">Teletypewriter (TTY) for the hearing impaired</h5> - <span class="t-block">202-219-3336</span> - </div> - </li> - <li class="contact-item contact-item--email"> - <div class="contact-item__content"> - <span class="t-block"><a href="mailto:info@fec.gov">info@fec.gov</a></span> - </div> - </li> - </ul> - <h2>Mailing address</h2> - <ul> - <li class="contact-item contact-item--mail"> - <div class="contact-item__content"> - <span class="t-block">Federal Election Commission</span> - <span class="t-block">1050 First Street, NE</span> - <span class="t-block">Washington, DC 20463</span> - </div> - </li> - </ul> - <h2>Website feedback</h2> - <ul> - <li class="contact-item contact-item--email"> - <div class="contact-item__content"> - <span class="t-block"><a href="mailto:webmanager@fec.gov">webmanager@fec.gov</a></span> - </div> - </li> - <li class="contact-item contact-item--github"> - <div class="contact-item__content"> - <span class="t-block"><a href="https://github.com/fecgov/fec/issues">GitHub repository</a></span> - </div> - </li> - </ul> - </div> - <div class="usa-width-two-thirds"> - <h2>Specific requests</h2> - <ul> - <li class="contact-item contact-item--half"> - <h5 class="contact-item__title">Congressional Affairs Office</h5> - <span class="t-block">FEC's primary liaison with Congress and executive branch agencies</span> - <span class="contact-item__phone">202-694-1006</span> - </li> - <li class="contact-item contact-item--half"> - <h5 class="contact-item__title">Electronic Filing Office</h5> - <span class="t-block">Including password assistance and technical support</span> - <span class="contact-item__phone">1-800-424-9530, menu option 4</span> - <span class="contact-item__phone">202-694-1307</span> - <span class="t-block"><a href="mailto:eFiletechsupport@fec.gov">eFiletechsupport@fec.gov</a></span> - </li> - <li class="contact-item contact-item--half"> - <h5 class="contact-item__title">Press Office</h5> - <span class="t-block">Including press queries and inquiries about Commission meetings</span> - <span class="contact-item__phone">1-800-424-9530, menu option 1</span> - <span class="contact-item__phone">202-694-1220</span> - <span class="t-block"><a href="mailto:press@fec.gov">press@fec.gov</a></span> - </li> - <li class="contact-item contact-item--half"> - <h5 class="contact-item__title">Information Division</h5> - <span class="t-block">Including help with campaign finance law, educational materials or registration and reporting forms</span> - <span class="contact-item__phone">1-800-424-9530, menu option 6</span> - <span class="contact-item__phone u-padding--bottom">202-694-1100</span> - <span class="t-block">Help with campaign finance law questions:</span> - <span class="t-block u-padding--bottom"> <a href="mailto:info@fec.gov">info@fec.gov</a></span> - <span class="t-block">Public appearance and FEC speaker requests:</span> - <span class="t-block u-padding--bottom"><a href="mailto:speaker@fec.gov">speaker@fec.gov</a></span> - <span class="t-block">Educational outreach and training inquiries:</span> - <span class="t-block"><a href="mailto:conferences@fec.gov">conferences@fec.gov</a></span> - </li> - <li class="contact-item contact-item--half"> - <h5 class="contact-item__title">Public Records Office</h5> - <span class="t-block">Including help accessing reports and other public documents</span> - <span class="contact-item__phone">1-800-424-9530, menu option 2</span> - <span class="contact-item__phone">202-694-1120</span> - <span class="t-block"><a href="mailto:pubrec@fec.gov">pubrec@fec.gov</a></span> - </li> - <li class="contact-item contact-item--half"> - <h5 class="contact-item__title">Reports Analysis Division</h5> - <span class="t-block">Including help with report creation, transactions and amendments </span> - <span class="t-block"><a href="/help-candidates-and-committees/question-rad">Contact your analyst</a> - <span class="contact-item__phone">1-800-424-9530, menu option 5</span> - <span class="contact-item__phone">202-694-1130</span> - </li> - </ul> - </div> - </div> -</div> -</article> -{% endblock %} diff --git a/fec/home/templates/home/contact_page.html b/fec/home/templates/home/contact_page.html index d72f7b0975..aa9d5199de 100644 --- a/fec/home/templates/home/contact_page.html +++ b/fec/home/templates/home/contact_page.html @@ -15,7 +15,7 @@ <h1>{{ self.title }}</h1> <div class="main__content--full"> <div class="usa-width-one-third u-padding--right"> {% for block in self.contact_items %} - {{ block.value }} + {{ block }} {% endfor %} </div> From d3db22e924cd72e2f749ce79308f31d8c754b239 Mon Sep 17 00:00:00 2001 From: john carroll <jcarroll@fec.gov> Date: Fri, 10 Aug 2018 16:33:49 -0400 Subject: [PATCH 12/49] revert inadvertently pushed changes --- fec/home/templates/home/contact.html | 121 ++++++++++++++++++++++ fec/home/templates/home/contact_page.html | 2 +- 2 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 fec/home/templates/home/contact.html diff --git a/fec/home/templates/home/contact.html b/fec/home/templates/home/contact.html new file mode 100644 index 0000000000..456617b83d --- /dev/null +++ b/fec/home/templates/home/contact.html @@ -0,0 +1,121 @@ +{% extends "base.html" %} +{% load wagtailcore_tags %} +{% load staticfiles %} +{% block body_class %}template-{{ self.get_verbose_name | slugify }}{% endblock %} + +{% block content %} +{% include 'partials/breadcrumbs.html' with page=self style='secondary' %} + +<div class="hero__banner--office"></div> +<article class="main"> + <div class="container"> + <header class="heading--main"> + <h1>{{ self.title }}</h1> + </header> + <div class="main__content--full u-padding--top"> + <div class="usa-width-one-third"> + <h2>General inquiries</h2> + <ul> + <li class="contact-item contact-item--phone"> + <div class="contact-item__content"> + <h5 class="contact-item__title">Toll free</h5> + <span class="t-block">1-800-424-9530</span> + <span class="t-block">8:30 a.m. to 5:30 p.m. Eastern Time</span> + </div> + </li> + <li class="contact-item"> + <div class="contact-item__content--offset"> + <h5 class="contact-item__title">Local</h5> + <span class="t-block">202-694-1000</span> + </div> + </li> + <li class="contact-item"> + <div class="contact-item__content--offset"> + <h5 class="contact-item__title">Teletypewriter (TTY) for the hearing impaired</h5> + <span class="t-block">202-219-3336</span> + </div> + </li> + <li class="contact-item contact-item--email"> + <div class="contact-item__content"> + <span class="t-block"><a href="mailto:info@fec.gov">info@fec.gov</a></span> + </div> + </li> + </ul> + <h2>Mailing address</h2> + <ul> + <li class="contact-item contact-item--mail"> + <div class="contact-item__content"> + <span class="t-block">Federal Election Commission</span> + <span class="t-block">1050 First Street, NE</span> + <span class="t-block">Washington, DC 20463</span> + </div> + </li> + </ul> + <h2>Website feedback</h2> + <ul> + <li class="contact-item contact-item--email"> + <div class="contact-item__content"> + <span class="t-block"><a href="mailto:webmanager@fec.gov">webmanager@fec.gov</a></span> + </div> + </li> + <li class="contact-item contact-item--github"> + <div class="contact-item__content"> + <span class="t-block"><a href="https://github.com/fecgov/fec/issues">GitHub repository</a></span> + </div> + </li> + </ul> + </div> + <div class="usa-width-two-thirds"> + <h2>Specific requests</h2> + <ul> + <li class="contact-item contact-item--half"> + <h5 class="contact-item__title">Congressional Affairs Office</h5> + <span class="t-block">FEC's primary liaison with Congress and executive branch agencies</span> + <span class="contact-item__phone">202-694-1006</span> + </li> + <li class="contact-item contact-item--half"> + <h5 class="contact-item__title">Electronic Filing Office</h5> + <span class="t-block">Including password assistance and technical support</span> + <span class="contact-item__phone">1-800-424-9530, menu option 4</span> + <span class="contact-item__phone">202-694-1307</span> + <span class="t-block"><a href="mailto:eFiletechsupport@fec.gov">eFiletechsupport@fec.gov</a></span> + </li> + <li class="contact-item contact-item--half"> + <h5 class="contact-item__title">Press Office</h5> + <span class="t-block">Including press queries and inquiries about Commission meetings</span> + <span class="contact-item__phone">1-800-424-9530, menu option 1</span> + <span class="contact-item__phone">202-694-1220</span> + <span class="t-block"><a href="mailto:press@fec.gov">press@fec.gov</a></span> + </li> + <li class="contact-item contact-item--half"> + <h5 class="contact-item__title">Information Division</h5> + <span class="t-block">Including help with campaign finance law, educational materials or registration and reporting forms</span> + <span class="contact-item__phone">1-800-424-9530, menu option 6</span> + <span class="contact-item__phone u-padding--bottom">202-694-1100</span> + <span class="t-block">Help with campaign finance law questions:</span> + <span class="t-block u-padding--bottom"> <a href="mailto:info@fec.gov">info@fec.gov</a></span> + <span class="t-block">Public appearance and FEC speaker requests:</span> + <span class="t-block u-padding--bottom"><a href="mailto:speaker@fec.gov">speaker@fec.gov</a></span> + <span class="t-block">Educational outreach and training inquiries:</span> + <span class="t-block"><a href="mailto:conferences@fec.gov">conferences@fec.gov</a></span> + </li> + <li class="contact-item contact-item--half"> + <h5 class="contact-item__title">Public Records Office</h5> + <span class="t-block">Including help accessing reports and other public documents</span> + <span class="contact-item__phone">1-800-424-9530, menu option 2</span> + <span class="contact-item__phone">202-694-1120</span> + <span class="t-block"><a href="mailto:pubrec@fec.gov">pubrec@fec.gov</a></span> + </li> + <li class="contact-item contact-item--half"> + <h5 class="contact-item__title">Reports Analysis Division</h5> + <span class="t-block">Including help with report creation, transactions and amendments </span> + <span class="t-block"><a href="/help-candidates-and-committees/question-rad">Contact your analyst</a> + <span class="contact-item__phone">1-800-424-9530, menu option 5</span> + <span class="contact-item__phone">202-694-1130</span> + </li> + </ul> + </div> + </div> +</div> +</article> +{% endblock %} diff --git a/fec/home/templates/home/contact_page.html b/fec/home/templates/home/contact_page.html index aa9d5199de..d72f7b0975 100644 --- a/fec/home/templates/home/contact_page.html +++ b/fec/home/templates/home/contact_page.html @@ -15,7 +15,7 @@ <h1>{{ self.title }}</h1> <div class="main__content--full"> <div class="usa-width-one-third u-padding--right"> {% for block in self.contact_items %} - {{ block }} + {{ block.value }} {% endfor %} </div> From f7b25b318441d7748a3a53320623aa968f92174d Mon Sep 17 00:00:00 2001 From: john carroll <jcarroll@fec.gov> Date: Fri, 10 Aug 2018 16:36:18 -0400 Subject: [PATCH 13/49] remove unused contact.html and fix contact_page.html template --- fec/home/templates/home/contact.html | 121 ---------------------- fec/home/templates/home/contact_page.html | 2 +- 2 files changed, 1 insertion(+), 122 deletions(-) delete mode 100644 fec/home/templates/home/contact.html diff --git a/fec/home/templates/home/contact.html b/fec/home/templates/home/contact.html deleted file mode 100644 index 456617b83d..0000000000 --- a/fec/home/templates/home/contact.html +++ /dev/null @@ -1,121 +0,0 @@ -{% extends "base.html" %} -{% load wagtailcore_tags %} -{% load staticfiles %} -{% block body_class %}template-{{ self.get_verbose_name | slugify }}{% endblock %} - -{% block content %} -{% include 'partials/breadcrumbs.html' with page=self style='secondary' %} - -<div class="hero__banner--office"></div> -<article class="main"> - <div class="container"> - <header class="heading--main"> - <h1>{{ self.title }}</h1> - </header> - <div class="main__content--full u-padding--top"> - <div class="usa-width-one-third"> - <h2>General inquiries</h2> - <ul> - <li class="contact-item contact-item--phone"> - <div class="contact-item__content"> - <h5 class="contact-item__title">Toll free</h5> - <span class="t-block">1-800-424-9530</span> - <span class="t-block">8:30 a.m. to 5:30 p.m. Eastern Time</span> - </div> - </li> - <li class="contact-item"> - <div class="contact-item__content--offset"> - <h5 class="contact-item__title">Local</h5> - <span class="t-block">202-694-1000</span> - </div> - </li> - <li class="contact-item"> - <div class="contact-item__content--offset"> - <h5 class="contact-item__title">Teletypewriter (TTY) for the hearing impaired</h5> - <span class="t-block">202-219-3336</span> - </div> - </li> - <li class="contact-item contact-item--email"> - <div class="contact-item__content"> - <span class="t-block"><a href="mailto:info@fec.gov">info@fec.gov</a></span> - </div> - </li> - </ul> - <h2>Mailing address</h2> - <ul> - <li class="contact-item contact-item--mail"> - <div class="contact-item__content"> - <span class="t-block">Federal Election Commission</span> - <span class="t-block">1050 First Street, NE</span> - <span class="t-block">Washington, DC 20463</span> - </div> - </li> - </ul> - <h2>Website feedback</h2> - <ul> - <li class="contact-item contact-item--email"> - <div class="contact-item__content"> - <span class="t-block"><a href="mailto:webmanager@fec.gov">webmanager@fec.gov</a></span> - </div> - </li> - <li class="contact-item contact-item--github"> - <div class="contact-item__content"> - <span class="t-block"><a href="https://github.com/fecgov/fec/issues">GitHub repository</a></span> - </div> - </li> - </ul> - </div> - <div class="usa-width-two-thirds"> - <h2>Specific requests</h2> - <ul> - <li class="contact-item contact-item--half"> - <h5 class="contact-item__title">Congressional Affairs Office</h5> - <span class="t-block">FEC's primary liaison with Congress and executive branch agencies</span> - <span class="contact-item__phone">202-694-1006</span> - </li> - <li class="contact-item contact-item--half"> - <h5 class="contact-item__title">Electronic Filing Office</h5> - <span class="t-block">Including password assistance and technical support</span> - <span class="contact-item__phone">1-800-424-9530, menu option 4</span> - <span class="contact-item__phone">202-694-1307</span> - <span class="t-block"><a href="mailto:eFiletechsupport@fec.gov">eFiletechsupport@fec.gov</a></span> - </li> - <li class="contact-item contact-item--half"> - <h5 class="contact-item__title">Press Office</h5> - <span class="t-block">Including press queries and inquiries about Commission meetings</span> - <span class="contact-item__phone">1-800-424-9530, menu option 1</span> - <span class="contact-item__phone">202-694-1220</span> - <span class="t-block"><a href="mailto:press@fec.gov">press@fec.gov</a></span> - </li> - <li class="contact-item contact-item--half"> - <h5 class="contact-item__title">Information Division</h5> - <span class="t-block">Including help with campaign finance law, educational materials or registration and reporting forms</span> - <span class="contact-item__phone">1-800-424-9530, menu option 6</span> - <span class="contact-item__phone u-padding--bottom">202-694-1100</span> - <span class="t-block">Help with campaign finance law questions:</span> - <span class="t-block u-padding--bottom"> <a href="mailto:info@fec.gov">info@fec.gov</a></span> - <span class="t-block">Public appearance and FEC speaker requests:</span> - <span class="t-block u-padding--bottom"><a href="mailto:speaker@fec.gov">speaker@fec.gov</a></span> - <span class="t-block">Educational outreach and training inquiries:</span> - <span class="t-block"><a href="mailto:conferences@fec.gov">conferences@fec.gov</a></span> - </li> - <li class="contact-item contact-item--half"> - <h5 class="contact-item__title">Public Records Office</h5> - <span class="t-block">Including help accessing reports and other public documents</span> - <span class="contact-item__phone">1-800-424-9530, menu option 2</span> - <span class="contact-item__phone">202-694-1120</span> - <span class="t-block"><a href="mailto:pubrec@fec.gov">pubrec@fec.gov</a></span> - </li> - <li class="contact-item contact-item--half"> - <h5 class="contact-item__title">Reports Analysis Division</h5> - <span class="t-block">Including help with report creation, transactions and amendments </span> - <span class="t-block"><a href="/help-candidates-and-committees/question-rad">Contact your analyst</a> - <span class="contact-item__phone">1-800-424-9530, menu option 5</span> - <span class="contact-item__phone">202-694-1130</span> - </li> - </ul> - </div> - </div> -</div> -</article> -{% endblock %} diff --git a/fec/home/templates/home/contact_page.html b/fec/home/templates/home/contact_page.html index d72f7b0975..aa9d5199de 100644 --- a/fec/home/templates/home/contact_page.html +++ b/fec/home/templates/home/contact_page.html @@ -15,7 +15,7 @@ <h1>{{ self.title }}</h1> <div class="main__content--full"> <div class="usa-width-one-third u-padding--right"> {% for block in self.contact_items %} - {{ block.value }} + {{ block }} {% endfor %} </div> From a65a586633df73b79013c9b1b92d0803df64d802 Mon Sep 17 00:00:00 2001 From: Laura Beaufort <31420082+lbeaufort@users.noreply.github.com> Date: Mon, 13 Aug 2018 10:07:47 -0400 Subject: [PATCH 14/49] Remove sort by district - API sorts by 'sort_order(P,S,H), district' --- fec/data/templates/election-lookup.jinja | 1 - 1 file changed, 1 deletion(-) diff --git a/fec/data/templates/election-lookup.jinja b/fec/data/templates/election-lookup.jinja index 971cb4dc71..a516d4fa0f 100644 --- a/fec/data/templates/election-lookup.jinja +++ b/fec/data/templates/election-lookup.jinja @@ -56,7 +56,6 @@ </select> </div> <div class="search-controls__submit"> - <input type="hidden" value="district" name="sort" /> <button type="submit" class="button--search--text button--standard">Search</button> </div> </fieldset> From 02de643dd278dc06169eff7ca3db6cd6c8f6f313 Mon Sep 17 00:00:00 2001 From: Laura Beaufort <31420082+lbeaufort@users.noreply.github.com> Date: Tue, 7 Aug 2018 17:06:48 -0400 Subject: [PATCH 15/49] Add logging for server-side API calls, formatter for logger --- fec/data/api_caller.py | 7 ++++++- fec/fec/settings/base.py | 6 ++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/fec/data/api_caller.py b/fec/data/api_caller.py index 94af8c4f2d..d8a2934670 100644 --- a/fec/data/api_caller.py +++ b/fec/data/api_caller.py @@ -1,8 +1,8 @@ import logging import os import re - import requests +import inspect from collections import OrderedDict from operator import itemgetter @@ -37,6 +37,11 @@ def _call_api(*path_parts, **filters): url = parse.urljoin(settings.FEC_API_URL, path) results = session.get(url, params=filters) + # Log the caller function and API endpoint + current_frame = inspect.currentframe() + caller_frame = inspect.getouterframes(current_frame, 2) + logger.info('{0}: {1}'.format(caller_frame[1][3], results.url)) + if results.ok: return results.json() else: diff --git a/fec/fec/settings/base.py b/fec/fec/settings/base.py index c815362b54..f51b56d351 100644 --- a/fec/fec/settings/base.py +++ b/fec/fec/settings/base.py @@ -282,10 +282,16 @@ LOGGING = { 'version': 1, 'disable_existing_loggers': False, + 'formatters': { + 'console': { + 'format': '%(levelname)s:%(name)s: %(message)s', + }, + }, 'handlers': { 'console': { 'class': 'logging.StreamHandler', 'level': 'INFO', + 'formatter': 'console', }, }, 'loggers': { From d2b929e706c6cb62bd54c71bcca6a142aa0f9f34 Mon Sep 17 00:00:00 2001 From: Laura Beaufort <31420082+lbeaufort@users.noreply.github.com> Date: Tue, 7 Aug 2018 17:07:13 -0400 Subject: [PATCH 16/49] Fix over-indentation, make formatting consistent --- fec/data/api_caller.py | 117 ++++++++++++++++++++++++++--------------- 1 file changed, 76 insertions(+), 41 deletions(-) diff --git a/fec/data/api_caller.py b/fec/data/api_caller.py index d8a2934670..b46e4826f5 100644 --- a/fec/data/api_caller.py +++ b/fec/data/api_caller.py @@ -7,7 +7,6 @@ from collections import OrderedDict from operator import itemgetter from urllib import parse - from django.conf import settings from django.http import Http404 @@ -16,11 +15,9 @@ MAX_FINANCIALS_COUNT = 4 - logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) - session = requests.Session() http_adapter = requests.adapters.HTTPAdapter(max_retries=2) session.mount('https://', http_adapter) @@ -181,7 +178,14 @@ def load_single_type(data_type, c_id, *path, **filters): def load_nested_type(parent_type, c_id, nested_type, *path, **filters): # Call API with nested types in load_with_nested - return _call_api(parent_type, c_id, nested_type, *path, per_page=100, **filters) + return _call_api( + parent_type, + c_id, + nested_type, + *path, + per_page=100, + **filters + ) def load_with_nested(primary_type, primary_id, secondary_type, cycle=None, @@ -196,7 +200,13 @@ def load_with_nested(primary_type, primary_id, secondary_type, cycle=None, """ Get data for just primary_type Example: candidate data for /candidate/* or committee data for /committee/* """ - data = load_single_type(primary_type, primary_id, *path, per_page=1, **query) + data = load_single_type( + primary_type, + primary_id, + *path, + per_page=1, + **query + ) cycle = cycle or max(data['cycles']) @@ -204,7 +214,13 @@ def load_with_nested(primary_type, primary_id, secondary_type, cycle=None, """ Get data for secondary_type Example: committee data for /candidate/P80003338/committees """ - nested_data = load_nested_type(primary_type, primary_id, secondary_type, *path, **query) + nested_data = load_nested_type( + primary_type, + primary_id, + secondary_type, + *path, + **query + ) return data, nested_data['results'], cycle @@ -228,17 +244,20 @@ def load_cmte_financials(committee_id, **filters): def load_candidate_totals(candidate_id, cycle, election_full=True): response = _call_api( - 'candidate', candidate_id, 'totals', - cycle=cycle, full_election=election_full, + 'candidate', + candidate_id, + 'totals', + cycle=cycle, + full_election=election_full, ) - return response['results'][0] if response['results'] else None def load_candidate_statement_of_candidacy(candidate_id, cycle): response = _call_api( 'filings', - candidate_id=candidate_id, form_type='F2' + candidate_id=candidate_id, + form_type='F2', ) # Cycle is always the even year; so to include odd year statements, @@ -246,7 +265,10 @@ def load_candidate_statement_of_candidacy(candidate_id, cycle): year = cycle - 1 if 'results' in response: - return [statement for statement in response['results'] if statement['election_year'] >= year] + return [ + statement for statement in response['results'] + if statement['election_year'] >= year + ] else: return [] @@ -258,33 +280,38 @@ def result_or_404(data): def load_top_candidates(sort, office=None, cycle=constants.DEFAULT_TIME_PERIOD, per_page=5): - response = _call_api( - 'candidates', 'totals', - sort_hide_null=True, - cycle=cycle, - election_full=False, - office=office, - sort=sort, - per_page=per_page - ) - - return response if 'results' in response else None + response = _call_api( + 'candidates', 'totals', + sort_hide_null=True, + cycle=cycle, + election_full=False, + office=office, + sort=sort, + per_page=per_page, + ) + return response if 'results' in response else None def load_top_pacs(sort, cycle=constants.DEFAULT_TIME_PERIOD, per_page=5): - response = _call_api( - 'totals', 'pac', - sort_hide_null=True, cycle=cycle, sort=sort, per_page=per_page - ) - return response if 'results' in response else None + response = _call_api( + 'totals', 'pac', + sort_hide_null=True, + cycle=cycle, + sort=sort, + per_page=per_page, + ) + return response if 'results' in response else None def load_top_parties(sort, cycle=constants.DEFAULT_TIME_PERIOD, per_page=5): - response = _call_api( - 'totals', 'party', - sort_hide_null=True, cycle=cycle, sort=sort, per_page=per_page - ) - return response if 'results' in response else None + response = _call_api( + 'totals', 'party', + sort_hide_null=True, + cycle=cycle, + sort=sort, + per_page=per_page, + ) + return response if 'results' in response else None def _get_sorted_participants_by_type(mur): @@ -323,13 +350,22 @@ def _get_sorted_participants_by_type(mur): return participants_by_type + def _get_sorted_documents(ao): """Sort documents within an AO by date DESC, description and document_id. We do this in 2 passes, making use of the fact that Python's `sorted` function performs a _stable_ sort. """ - sorted_documents = sorted(ao['documents'], key=itemgetter('description', 'document_id'), reverse=False) - sorted_documents = sorted(sorted_documents, key=itemgetter('date'), reverse=True) + sorted_documents = sorted( + ao['documents'], + key=itemgetter('description', 'document_id'), + reverse=False, + ) + sorted_documents = sorted( + sorted_documents, + key=itemgetter('date'), + reverse=True, + ) return sorted_documents @@ -338,11 +374,12 @@ def call_senate_specials(state): given state. Returns a list of dictionaries Example: [{details for election1}][{details for election2}] """ - api_response = _call_api('election-dates', - election_type_id='SG', - office_sought='S', - election_state=state) - + api_response = _call_api( + 'election-dates', + election_type_id='SG', + office_sought='S', + election_state=state, + ) special_results = api_response['results'] return api_response.get('results', None) @@ -356,10 +393,8 @@ def format_special_results(special_results=[]): senate_specials = [] for result in special_results: - # Round odd years up to even years result['election_year'] = result['election_year'] + (result['election_year'] % 2) - senate_specials.append(result.get('election_year', None)) return senate_specials From 46fb8be1914c1a4625fd86dc4f2c617ccfb64d3f Mon Sep 17 00:00:00 2001 From: Laura Beaufort <31420082+lbeaufort@users.noreply.github.com> Date: Tue, 7 Aug 2018 17:08:37 -0400 Subject: [PATCH 17/49] Fix unused variable --- fec/data/api_caller.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fec/data/api_caller.py b/fec/data/api_caller.py index b46e4826f5..9d9f58be7a 100644 --- a/fec/data/api_caller.py +++ b/fec/data/api_caller.py @@ -374,15 +374,15 @@ def call_senate_specials(state): given state. Returns a list of dictionaries Example: [{details for election1}][{details for election2}] """ - api_response = _call_api( + special_results = _call_api( 'election-dates', election_type_id='SG', office_sought='S', election_state=state, ) - special_results = api_response['results'] - return api_response.get('results', None) + return special_results.get('results') + def format_special_results(special_results=[]): From 0510ca8ce56219dc00d9fd5208be167372855ceb Mon Sep 17 00:00:00 2001 From: Vraj Mohan <radhakrishnan.vrajmohan@gsa.gov> Date: Thu, 9 Aug 2018 13:14:11 -0700 Subject: [PATCH 18/49] Provide test coverage reports --- .circleci/config.yml | 3 ++- README.md | 7 +++++++ requirements.txt | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index a0c47771a4..6d7c4cae0a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -75,7 +75,8 @@ jobs: npm run build-sass cd fec DJANGO_SETTINGS_MODULE=fec.settings.production python manage.py collectstatic --noinput -v 0 - ./manage.py test + coverage run --source='.' manage.py test + coverage xml --omit="*migrations*" npm run test-single - run: diff --git a/README.md b/README.md index be2fa2d7ed..09d93cda14 100644 --- a/README.md +++ b/README.md @@ -191,6 +191,13 @@ command line, e.g.: env DATABASE_URL=postgresql://:@/cfdm_cms_test ./manage.py test ``` +For test coverage, run: +```bash +cd fec/ +coverage run --source='.' manage.py test +coverage report --omit="*migrations*" +``` + ## Enabling/toggling features [settings/base.py](https://github.com/fecgov/fec-cms/blob/develop/fec/fec/settings/base.py) includes a set of `FEATURES` which can also be enabled using environment flags: diff --git a/requirements.txt b/requirements.txt index f9b9f3aadc..f807066189 100644 --- a/requirements.txt +++ b/requirements.txt @@ -38,5 +38,6 @@ boto3==1.7.21 cg-django-uaa==1.2.0 # Testing +coverage==4.5.1 pytest==3.2.3 Faker==0.8.6 From a285d2dba321170da00096e7bb28bd4b9bedaa1c Mon Sep 17 00:00:00 2001 From: Vraj Mohan <radhakrishnan.vrajmohan@gsa.gov> Date: Mon, 13 Aug 2018 10:03:46 -0700 Subject: [PATCH 19/49] Add code coverage badges --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 09d93cda14..12901edea2 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ **Develop** [![CircleCI](https://circleci.com/gh/fecgov/fec-cms.svg?style=svg)](https://circleci.com/gh/fecgov/fec-cms) +[![Test Coverage](https://img.shields.io/codecov/c/github/fecgov/fec-cms/develop.svg)](https://codecov.io/github/fecgov/fec-cms) **Master** [![Known Vulnerabilities](https://snyk.io/test/github/fecgov/fec-cms/badge.svg)](https://snyk.io/test/github/fecgov/fec-cms) +[![Test Coverage](https://img.shields.io/codecov/c/github/fecgov/fec-cms/master.svg)](https://codecov.io/github/fecgov/fec-cms) ## Campaign finance for everyone The Federal Election Commission (FEC) releases information to the public about From 49467e53af59f0197f40554089b2ff4f31772e55 Mon Sep 17 00:00:00 2001 From: Jun Li <jli@fec.gov> Date: Mon, 13 Aug 2018 13:13:46 -0400 Subject: [PATCH 20/49] Add new app: checkweb 1)check export(download) function 2)get api information --- fec/checkweb/__init__.py | 0 fec/checkweb/admin.py | 3 ++ fec/checkweb/apps.py | 5 ++ fec/checkweb/templates/checkweb.jinja | 45 ++++++++++++++++ .../macros/page-header-checkweb.jinja | 11 ++++ .../templates/partials/api-info.jinja | 10 ++++ .../templates/partials/check-export.jinja | 54 +++++++++++++++++++ .../partials/checkweb-sidebar-nav.jinja | 20 +++++++ .../partials/election-candidate-totals.jinja | 41 ++++++++++++++ fec/checkweb/tests.py | 3 ++ fec/checkweb/urls.py | 7 +++ fec/checkweb/views.py | 17 ++++++ fec/fec/settings/base.py | 47 +++++++++++----- fec/fec/urls.py | 30 ++++++++--- 14 files changed, 272 insertions(+), 21 deletions(-) create mode 100644 fec/checkweb/__init__.py create mode 100644 fec/checkweb/admin.py create mode 100644 fec/checkweb/apps.py create mode 100644 fec/checkweb/templates/checkweb.jinja create mode 100644 fec/checkweb/templates/macros/page-header-checkweb.jinja create mode 100644 fec/checkweb/templates/partials/api-info.jinja create mode 100644 fec/checkweb/templates/partials/check-export.jinja create mode 100644 fec/checkweb/templates/partials/checkweb-sidebar-nav.jinja create mode 100644 fec/checkweb/templates/partials/election-candidate-totals.jinja create mode 100644 fec/checkweb/tests.py create mode 100644 fec/checkweb/urls.py create mode 100644 fec/checkweb/views.py diff --git a/fec/checkweb/__init__.py b/fec/checkweb/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/fec/checkweb/admin.py b/fec/checkweb/admin.py new file mode 100644 index 0000000000..8c38f3f3da --- /dev/null +++ b/fec/checkweb/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/fec/checkweb/apps.py b/fec/checkweb/apps.py new file mode 100644 index 0000000000..39c479e0b9 --- /dev/null +++ b/fec/checkweb/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class TestinfoConfig(AppConfig): + name = 'checkweb' diff --git a/fec/checkweb/templates/checkweb.jinja b/fec/checkweb/templates/checkweb.jinja new file mode 100644 index 0000000000..58f987339b --- /dev/null +++ b/fec/checkweb/templates/checkweb.jinja @@ -0,0 +1,45 @@ +{% extends 'layouts/main.jinja' %} +{% import 'macros/page-header-checkweb.jinja' as header %} + +{% block title %}{{ title }}{% endblock %} + +{% block css %} + <link rel="stylesheet" type="text/css" href="{{ asset_for_css('elections.css') }}" /> +{% endblock %} + +{% block body %} + {{ header.header('check web') }} + <div class="u-padding--left u-padding--right tab-interface"> + <header class="main"> + <h1 class="entity__name">Check FEC website</h1> + <div class="heading--section"> + <span class="t-data t-bold"></span> + </div> + </header> + <div> + {% include 'partials/checkweb-sidebar-nav.jinja' %} + <div class="main__content--right-full"> + <section id="section-1" role="tabpanel" aria-hidden="true" aria-labelledby="section-1-heading"> + {% include 'partials/election-candidate-totals.jinja' %} + {% include 'partials/api-info.jinja' %} + </section> + </div> + </div> + </div> +{% endblock %} + +{% block scripts %} +<script> +var context = { + election: { + cycle: '{{ cycle }}', + office: '{{ office }}', + state: '{{ state or '' }}', + stateFull: '{{ state_full or '' }}', + district: '{{ district or '' }}' + } +}; +</script> +<script src="{{ asset_for_js('dataviz-common.js') }}"></script> +<script src="{{ asset_for_js('elections.js') }}"></script> +{% endblock %} diff --git a/fec/checkweb/templates/macros/page-header-checkweb.jinja b/fec/checkweb/templates/macros/page-header-checkweb.jinja new file mode 100644 index 0000000000..728b2455e3 --- /dev/null +++ b/fec/checkweb/templates/macros/page-header-checkweb.jinja @@ -0,0 +1,11 @@ +{% macro header(title) %} +<div class="page-header page-header--primary"> + <ul class="breadcrumbs"> + <li class="breadcrumbs__item"><a href="/" class="breadcrumbs__link" rel="Home">Home</a></li> + <li class="breadcrumbs__item breadcrumbs__item--current"> + <span class="breadcrumbs__separator">›</span> + {{ title }} + </li> + </ul> +</div> +{% endmacro %} diff --git a/fec/checkweb/templates/partials/api-info.jinja b/fec/checkweb/templates/partials/api-info.jinja new file mode 100644 index 0000000000..2fd424ed5b --- /dev/null +++ b/fec/checkweb/templates/partials/api-info.jinja @@ -0,0 +1,10 @@ +<div class='container'> + <h2 id="section-1-heading">(2)Check API information</h2> + <div class="slab slab--inline slab--neutral u-padding--left u-padding--right"> + <div id="api-info" class="entity__figure row"> + <div class="heading--section heading--with-action u-no-margin"> + <h3 class="heading__left">api ==> {{ api_name }}</h3> + </div> + </div> + </div> +</div> diff --git a/fec/checkweb/templates/partials/check-export.jinja b/fec/checkweb/templates/partials/check-export.jinja new file mode 100644 index 0000000000..3d8dcc7ba4 --- /dev/null +++ b/fec/checkweb/templates/partials/check-export.jinja @@ -0,0 +1,54 @@ +<section id="section-2" role="tabpanel" aria-hidden="true" aria-labelledby="section-2-heading"> + <div class="container"> + <h2 id="section-2-heading">About this election</h2> + <div class="entity__figure u-no-padding row election-dates"> + </div> + {% include 'partials/elections/election-profile-cards.jinja' %} + <div class="slab slab--inline slab--neutral u-padding--left u-padding--right"> + <div id="candidate-information" class="entity__figure row"> + <div class="heading--section heading--with-action u-no-margin"> + <h3 class="heading__left">Candidate information</h3> + <div class="heading__right"> + <span class="is-incumbent t-sans t-note" style="padding-right: 1em">Incumbent</span> + <div + class="message message--info message--mini t-left-aligned data-container__message" + data-export-message-for="candidate-information" + aria-hidden="false" + > + Exports coming soon. + </div> + <button + type="button" + class="js-export button button--cta button--export" + data-export-for="candidate-information" + > + Export + </button> + </div> + </div> + <table + id="candidate-information-results" + class="data-table data-table--heading-borders scrollX" + data-type="candidate-information" + > + <thead> + <th role="col">Name</th> + <th role="col">Party</th> + <th role="col">Principal campaign committee</th> + </thead> + </table> + </div> + <div id="state-election-offices" class="entity__figure row"> + <div class="heading--section heading--with-action"> + <h3 class="heading__left">State election offices</h3> + </div> + {% if office == 'president' %} + <p>Ballot access procedures for presidential candidates vary by state. Contact each <a href="https://transition.fec.gov/pubrec/stateelection.shtml">state election office</a> for information.</p> + {% else %} + <p>State laws and procedures govern elections for state or local offices as well as how candidates come to appear on election ballots. Contact the appropriate state election office for more information.</p> + <div id="election-offices"></div> + {% endif %} + </div> + </div> + </div> +</section> diff --git a/fec/checkweb/templates/partials/checkweb-sidebar-nav.jinja b/fec/checkweb/templates/partials/checkweb-sidebar-nav.jinja new file mode 100644 index 0000000000..33c5e725fa --- /dev/null +++ b/fec/checkweb/templates/partials/checkweb-sidebar-nav.jinja @@ -0,0 +1,20 @@ +<nav class="sidebar side-nav-alt"> + <ul class="tablist" role="tablist" data-name="tab"> + <li class="side-nav__item" role="presentation"> + <a + class="side-nav__link" + role="tab" + data-name="election-data" + tabindex="0" + aria-controls="panel1" + href="#section-1" + > + Check FEC website + </a> + <ul> + <li><a href="#candidate-financial-totals">(1)Check export function</a></li> + <li><a href="#api-info">(2)Check API info</a></li> + </ul> + </li> + </ul> +</nav> \ No newline at end of file diff --git a/fec/checkweb/templates/partials/election-candidate-totals.jinja b/fec/checkweb/templates/partials/election-candidate-totals.jinja new file mode 100644 index 0000000000..62a4db1e0d --- /dev/null +++ b/fec/checkweb/templates/partials/election-candidate-totals.jinja @@ -0,0 +1,41 @@ +<div class='container'> + <h2 id="section-1-heading">(1)Check download/export function</h2> + <div class="slab slab--inline slab--neutral u-padding--left u-padding--right"> + + <div id="candidate-financial-totals" class="entity__figure row"> + <div class="heading--section heading--with-action u-no-margin"> + <h3 class="heading__left">Candidate financial totals ({{cycle}}--{{state}}--{{office}})</h3> + <div class="heading__right"> + <span class="is-incumbent t-sans t-note" style="padding-right: 1em">Incumbent</span> + <div + class="message message--info message--mini t-left-aligned data-container__message" + data-export-message-for="candidate-financial-totals" + aria-hidden="false" + > + Exports coming soon. + </div> + <button + type="button" + class="js-export button button--cta button--export" + data-export-for="candidate-financial-totals" + > + Export + </button> + </div> + </div> + <table + class="data-table data-table--heading-borders scrollX" + data-type="candidate-financial-totals" + > + <thead> + <th role="col">Candidate</th> + <th role="col">Party</th> + <th role="col">Total receipts</th> + <th role="col">Total disbursements</th> + <th role="col">Cash on hand</th> + <th role="col">Source reports</th> + </thead> + </table> + </div> + </div> +</div> diff --git a/fec/checkweb/tests.py b/fec/checkweb/tests.py new file mode 100644 index 0000000000..7ce503c2dd --- /dev/null +++ b/fec/checkweb/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/fec/checkweb/urls.py b/fec/checkweb/urls.py new file mode 100644 index 0000000000..469bb85fc8 --- /dev/null +++ b/fec/checkweb/urls.py @@ -0,0 +1,7 @@ +from django.conf.urls import url + +from checkweb import views + +urlpatterns = [ + url(r'^checkweb/$', views.checkweb), +] diff --git a/fec/checkweb/views.py b/fec/checkweb/views.py new file mode 100644 index 0000000000..4bc62aff8a --- /dev/null +++ b/fec/checkweb/views.py @@ -0,0 +1,17 @@ +from django.shortcuts import render +from data import constants +from django.conf import settings + + +def checkweb(request, office='senate', cycle=2018, state='VA', district=None): + return render(request, 'checkweb.jinja', { + 'office': office, + 'office_code': office[0], + 'parent': 'checkweb', + 'cycle': int(cycle), + 'state': state, + 'state_full': constants.states[state.upper()] if state else None, + 'district': district, + 'title': 'check website', + 'api_name': settings.FEC_API_URL, + }) diff --git a/fec/fec/settings/base.py b/fec/fec/settings/base.py index c815362b54..8237d81eed 100644 --- a/fec/fec/settings/base.py +++ b/fec/fec/settings/base.py @@ -32,13 +32,22 @@ FEC_SERVICE_NOW_USERNAME = env.get_credential('FEC_SERVICE_NOW_USERNAME') FEC_SERVICE_NOW_PASSWORD = env.get_credential('FEC_SERVICE_NOW_PASSWORD') FEC_DIGITALGOV_KEY = env.get_credential('FEC_DIGITALGOV_KEY') -FEC_DIGITALGOV_DRAWER_KEY_MAIN = env.get_credential('DIGITALGOV_DRAWER_KEY_MAIN', '') -FEC_DIGITALGOV_DRAWER_KEY_TRANSITION = env.get_credential('DIGITALGOV_DRAWER_KEY_TRANSITION', '') +FEC_DIGITALGOV_DRAWER_KEY_MAIN = env.get_credential( + 'DIGITALGOV_DRAWER_KEY_MAIN', '' +) +FEC_DIGITALGOV_DRAWER_KEY_TRANSITION = env.get_credential( + 'DIGITALGOV_DRAWER_KEY_TRANSITION', '' +) DIGITALGOV_BASE_API_URL = 'https://i14y.usa.gov/api/v1' DIGITALGOV_DRAWER_HANDLE = 'main' -FEC_TRANSITION_URL = env.get_credential('FEC_TRANSITION_URL', 'https://transition.fec.gov') -FEC_CLASSIC_URL = env.get_credential('FEC_CLASSIC_URL', 'http://classic.fec.gov') +FEC_TRANSITION_URL = env.get_credential( + 'FEC_TRANSITION_URL', 'https://transition.fec.gov' +) +FEC_CLASSIC_URL = env.get_credential( + 'FEC_CLASSIC_URL', + 'http://classic.fec.gov' +) FEATURES = { 'record': bool(env.get_credential('FEC_FEATURE_RECORD', '')), @@ -55,7 +64,10 @@ 'prod': 'PRODUCTION', 'feature': 'FEATURE', } -FEC_CMS_ENVIRONMENT = ENVIRONMENTS.get(env.get_credential('FEC_CMS_ENVIRONMENT'), 'LOCAL') +FEC_CMS_ENVIRONMENT = ENVIRONMENTS.get( + env.get_credential('FEC_CMS_ENVIRONMENT'), + 'LOCAL' +) CONTACT_EMAIL = 'webmanager@fec.gov' WEBMANAGER_EMAIL = "webmanager@fec.gov" @@ -97,6 +109,7 @@ 'home', 'data', 'legal', + 'checkweb', 'uaa_client', 'extend_admin', ) @@ -123,7 +136,6 @@ from data import constants - TEMPLATES = [ { 'BACKEND': 'django_jinja.backend.Jinja2', @@ -263,12 +275,19 @@ AWS_LOCATION = 'cms-content' UAA_CLIENT_ID = env.get_credential('CMS_LOGIN_CLIENT_ID', 'my-client-id') -UAA_CLIENT_SECRET = env.get_credential('CMS_LOGIN_CLIENT_SECRET', 'my-client-secret') -#fake uaa server deploys locally on port 8080. Will be needed to login for local use -#TODO: These will have to have a explicit reference until we can figure out how -#to silence django warnings about the url being http (it expects https). -#UAA_AUTH_URL = env.get_credential('CMS_LOGIN_AUTH_URL', 'http://localhost:8080/oauth/authorize') -#UAA_TOKEN_URL = env.get_credential('CMS_LOGIN_TOKEN_URL','http://localhost:8080/oauth/token') +UAA_CLIENT_SECRET = env.get_credential( + 'CMS_LOGIN_CLIENT_SECRET', + 'my-client-secret' +) +# fake uaa server deploys locally on port 8080. +# Will be needed to login for local use +# TODO: These will have to have a explicit reference +# until we can figure out how +# to silence django warnings about the url being http (it expects https). +# UAA_AUTH_URL = env.get_credential('CMS_LOGIN_AUTH_URL', +# 'http://localhost:8080/oauth/authorize') +# UAA_TOKEN_URL = env.get_credential('CMS_LOGIN_TOKEN_URL', +# 'http://localhost:8080/oauth/token') UAA_AUTH_URL = 'https://login.fr.cloud.gov/oauth/authorize' UAA_TOKEN_URL = 'https://login.fr.cloud.gov/oauth/token' WAGTAIL_FRONTEND_LOGIN_URL = 'uaa_client:login' @@ -277,7 +296,9 @@ ['django.contrib.auth.backends.ModelBackend', 'uaa_client.authentication.UaaBackend'] -DEFAULT_AUTHENTICATION_CLASSES = ['rest_framework_jwt.authentication.JSONWebTokenAuthentication',] +DEFAULT_AUTHENTICATION_CLASSES = [ + 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', +] LOGGING = { 'version': 1, diff --git a/fec/fec/urls.py b/fec/fec/urls.py index 20e588e4b1..44d88ebb4a 100644 --- a/fec/fec/urls.py +++ b/fec/fec/urls.py @@ -14,25 +14,37 @@ urlpatterns = [ - url(r'^documents/(\d+)/(.*)$', home_views.serve_wagtail_doc, name='wagtaildocs_serve'), + url( + r'^documents/(\d+)/(.*)$', + home_views.serve_wagtail_doc, + name='wagtaildocs_serve' + ), url(r'^auth/', include(uaa_urls)), url(r'^admin/', include(wagtailadmin_urls)), url(r'^calendar/$', home_views.calendar), - url(r'^about/leadership-and-structure/commissioners/$', home_views.commissioners), + url( + r'^about/leadership-and-structure/commissioners/$', + home_views.commissioners + ), url(r'^documents/', include(wagtaildocs_urls)), - url(r'^help-candidates-and-committees/question-rad/$', home_views.contact_rad), + url( + r'^help-candidates-and-committees/question-rad/$', + home_views.contact_rad + ), url(r'^help-candidates-and-committees/guides/$', home_views.guides), url(r'^meetings/$', home_views.index_meetings, name="meetings_page"), url(r'^search/$', search_views.search, name='search'), url(r'^updates/$', home_views.updates), url(r'', include('data.urls')), # URLs for /data url(r'', include('legal.urls')), # URLs for legal pages + url(r'', include('checkweb.urls')), # URL for check web url(r'', include(wagtail_urls)), ] if settings.FEC_CMS_ENVIRONMENT != 'LOCAL': - #admin/login always must come before admin/, so place at beginning of list - urlpatterns.insert(0,url(r'^admin/login', uaa_views.login, name='login')) + # admin/login always must come before admin/, + # so place at beginning of list + urlpatterns.insert(0, url(r'^admin/login', uaa_views.login, name='login')) if settings.FEC_CMS_ROBOTS: urlpatterns += url( @@ -50,7 +62,9 @@ # Serve static and media files from development server urlpatterns += staticfiles_urlpatterns() - urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + urlpatterns += static( + settings.MEDIA_URL, document_root=settings.MEDIA_ROOT + ) - #hide django-admin unless DEBUG=True - urlpatterns.insert(1,url(r'^django-admin/', include(admin.site.urls))) + # hide django-admin unless DEBUG=True + urlpatterns.insert(1, url(r'^django-admin/', include(admin.site.urls))) From 66e540a86391dcd11d197977bf047f7f34eb084c Mon Sep 17 00:00:00 2001 From: Patcharee Phongsvirajati <pphongsvirajati@fec.gov> Date: Tue, 14 Aug 2018 09:21:01 -0400 Subject: [PATCH 21/49] removed <a> tag from anchor component and added color highlighting in editor for anchor component --- fec/fec/draftail/anchor.py | 8 -------- fec/fec/static/js/draftail/Anchor.js | 7 +------ fec/fec/static/js/init.js | 4 ---- 3 files changed, 1 insertion(+), 18 deletions(-) diff --git a/fec/fec/draftail/anchor.py b/fec/fec/draftail/anchor.py index 5ae576031a..2c21b11a43 100644 --- a/fec/fec/draftail/anchor.py +++ b/fec/fec/draftail/anchor.py @@ -6,14 +6,6 @@ def anchor_entity_decorator(props): return DOM.create_element('span', { 'data-anchor': props['anchor'], 'id': props['anchor'], - 'style': { - 'fontSize': '2.4rem', - 'margin': '0 0 1em 0', - 'fontFamily': 'gandhi,serif', - 'fontSize': '2rem', - 'fontWeight': '700', - 'lineHeight': '1.2' - } , }, props['children']) diff --git a/fec/fec/static/js/draftail/Anchor.js b/fec/fec/static/js/draftail/Anchor.js index 207e34915b..3feafef1a2 100644 --- a/fec/fec/static/js/draftail/Anchor.js +++ b/fec/fec/static/js/draftail/Anchor.js @@ -43,12 +43,7 @@ class AnchorSource extends React.Component { const Anchor = ({children}) => ( <span style={{ - fontSize: '2.4rem', - margin: '0 0 1em 0', - fontFamily: 'gandhi,serif', - fontSize: '2rem', - fontWeight: '700', - lineHeight: '1.2' + backgroundColor: '#00FFFF', }} > {children} diff --git a/fec/fec/static/js/init.js b/fec/fec/static/js/init.js index 39db5b2770..af29de7042 100644 --- a/fec/fec/static/js/init.js +++ b/fec/fec/static/js/init.js @@ -103,8 +103,4 @@ $(document).ready(function() { $link.remove(); } }); - - // helpers.anchorify - // Add 'a' href to anchor links - helpers.anchorify('data-anchor'); }); From 2a3155e7b12526cb9dc741c66694a752360aa949 Mon Sep 17 00:00:00 2001 From: Patcharee Phongsvirajati <pphongsvirajati@fec.gov> Date: Tue, 14 Aug 2018 11:21:18 -0400 Subject: [PATCH 22/49] re-adds wagtailcore dependency to initial, re-adds python tests to circle config, removed additional wagtail app migrations as those have already been applied on deploy to prod, updated tests to include test coverage output --- .circleci/config.yml | 2 ++ README.md | 7 +++++++ bin/run.sh | 10 ---------- fec/home/migrations/0001_initial.py | 10 +++++++++- requirements.txt | 1 + 5 files changed, 19 insertions(+), 11 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index bf9d7a3798..6d7c4cae0a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -75,6 +75,8 @@ jobs: npm run build-sass cd fec DJANGO_SETTINGS_MODULE=fec.settings.production python manage.py collectstatic --noinput -v 0 + coverage run --source='.' manage.py test + coverage xml --omit="*migrations*" npm run test-single - run: diff --git a/README.md b/README.md index be2fa2d7ed..09d93cda14 100644 --- a/README.md +++ b/README.md @@ -191,6 +191,13 @@ command line, e.g.: env DATABASE_URL=postgresql://:@/cfdm_cms_test ./manage.py test ``` +For test coverage, run: +```bash +cd fec/ +coverage run --source='.' manage.py test +coverage report --omit="*migrations*" +``` + ## Enabling/toggling features [settings/base.py](https://github.com/fecgov/fec-cms/blob/develop/fec/fec/settings/base.py) includes a set of `FEATURES` which can also be enabled using environment flags: diff --git a/bin/run.sh b/bin/run.sh index aa347e5e3c..581309150f 100755 --- a/bin/run.sh +++ b/bin/run.sh @@ -11,16 +11,6 @@ set -o pipefail cd fec # Run migrations ./manage.py makemigrations -./manage.py migrate wagtailadmin -./manage.py migrate wagtailcore -./manage.py migrate wagtaildocs -./manage.py migrate wagtailembeds -./manage.py migrate wagtailforms -./manage.py migrate wagtailimages -./manage.py migrate wagtailredirects -./manage.py migrate wagtailsearch -./manage.py migrate wagtailsearchpromotions -./manage.py migrate wagtailusers ./manage.py migrate --noinput # Run application diff --git a/fec/home/migrations/0001_initial.py b/fec/home/migrations/0001_initial.py index bd16119f6f..203b2f8633 100644 --- a/fec/home/migrations/0001_initial.py +++ b/fec/home/migrations/0001_initial.py @@ -5,11 +5,19 @@ class Migration(migrations.Migration): + dependencies = [('wagtailcore', '0040_page_draft_title')] + operations = [ migrations.CreateModel( name='HomePage', fields=[ - ('page_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='wagtailcore.Page')), + ('page_ptr', + models.OneToOneField( + parent_link=True, + auto_created=True, + primary_key=True, + serialize=False, + to='wagtailcore.Page')), ], options={ 'abstract': False, diff --git a/requirements.txt b/requirements.txt index f9b9f3aadc..f807066189 100644 --- a/requirements.txt +++ b/requirements.txt @@ -38,5 +38,6 @@ boto3==1.7.21 cg-django-uaa==1.2.0 # Testing +coverage==4.5.1 pytest==3.2.3 Faker==0.8.6 From 41f3f39b2834d4ea8fd0195d98eabf628062a5d1 Mon Sep 17 00:00:00 2001 From: Laura Beaufort <31420082+lbeaufort@users.noreply.github.com> Date: Thu, 16 Aug 2018 13:12:11 -0400 Subject: [PATCH 23/49] Add code.json file for code.gov open source inventory --- README.md | 6 + fec/fec/templates/code.json | 317 ++++++++++++++++++++++++++++++++++++ fec/fec/urls.py | 5 + 3 files changed, 328 insertions(+) create mode 100644 fec/fec/templates/code.json diff --git a/README.md b/README.md index 12901edea2..cea064cbc8 100644 --- a/README.md +++ b/README.md @@ -236,6 +236,12 @@ Lastly run migrations to account for any very recent changes that are not presen run this command: `./manage.py migrate` +### Generating code.json + +Code.gov uses the code.json file located at fec.gov/code.json to inventory our repositories. The file is generated using [LLNL's scraper tool](https://github.com/LLNL/scraper). Follow the instructions in scraper's README file to generate a new code.json, or manually update as needed. + +Examples of code.json files: https://github.com/GSA/code-gov/blob/master/METADATA_EXAMPLES.md + ## Deploy *Likely only useful for FEC team members* diff --git a/fec/fec/templates/code.json b/fec/fec/templates/code.json new file mode 100644 index 0000000000..83ebfd2b51 --- /dev/null +++ b/fec/fec/templates/code.json @@ -0,0 +1,317 @@ +{ + "agency": "FEC", + "measurementType": { + "ifOther": "TBD", + "method": "other" + }, + "releases": [ + { + "organization": "Federal Election Commission", + "contact": { + "URL": "https://github.com/fecgov/fec/issues", + "email": "opensource@fec.gov" + }, + "date": { + "created": "2018-08-03T12:50:00+00:00", + "lastModified": "2018-08-03T12:49:57+00:00", + "metadataLastUpdated": "2018-08-03T12:49:57+00:00" + }, + "description": "A general discussion forum for FEC.gov. This is the best place to submit general feedback.", + "downloadURL": "https://api.github.com/repos/fecgov/FEC/downloads", + "homepageURL": "https://github.com/fecgov/FEC", + "laborHours": 1092.0, + "languages": [], + "name": "FEC", + "permissions": { + "usageType": "openSource", + "licenses": [ + { + "URL": "https://github.com/fecgov/FEC/blob/master/LICENSE.md", + "name": "CC0" + } + ] + }, + "repositoryURL": "https://github.com/fecgov/FEC", + "status": "Production", + "tags": [ + "FEC", + "github", + "feedback", + "discussion" + ], + "vcs": "git" + }, + { + "organization": "Federal Election Commission", + "contact": { + "URL": "https://github.com/fecgov/fec/issues", + "email": "opensource@fec.gov" + }, + "date": { + "created": "2018-08-07T14:55:33+00:00", + "lastModified": "2018-08-06T20:48:06+00:00", + "metadataLastUpdated": "2018-08-03T12:49:57+00:00" + }, + "description": "The first RESTful API for the Federal Election Commission. We're aiming to make campaign finance more accessible for journalists, academics, developers, and other transparency seekers.", + "downloadURL": "https://api.github.com/repos/fecgov/openFEC/downloads", + "homepageURL": "https://api.open.fec.gov/developers/", + "laborHours": 204966.6666666667, + "languages": [ + "Python", + "PLpgSQL", + "JavaScript", + "HTML", + "SQLPL", + "Shell", + "CSS" + ], + "name": "openFEC", + "permissions": { + "usageType": "openSource", + "licenses": [ + { + "URL": "https://github.com/fecgov/openFEC/blob/master/LICENSE.md", + "name": "CC0" + } + ] + }, + "repositoryURL": "https://github.com/fecgov/openFEC", + "status": "Production", + "tags": [ + "FEC", + "github", + "API", + "campaign finance" + ], + "vcs": "git" + }, + { + "organization": "Federal Election Commission", + "contact": { + "URL": "https://github.com/fecgov/fec/issues", + "email": "opensource@fec.gov" + }, + "date": { + "created": "2018-08-08T04:21:05+00:00", + "lastModified": "2018-08-06T16:12:26+00:00", + "metadataLastUpdated": "2018-08-03T12:49:57+00:00" + }, + "description": "The content management system (CMS) for the new Federal Election Commission website.", + "downloadURL": "https://api.github.com/repos/fecgov/fec-cms/downloads", + "homepageURL": "https://www.fec.gov", + "laborHours": 46696.0, + "languages": [ + "HTML", + "JavaScript", + "Python", + "CSS", + "Shell" + ], + "name": "fec-cms", + "permissions": { + "usageType": "openSource", + "licenses": [ + { + "URL": "https://github.com/fecgov/fec-cms/blob/master/LICENSE.md", + "name": "CC0" + } + ] + }, + "repositoryURL": "https://github.com/fecgov/fec-cms", + "relatedCode": [ + { + "name": "U.S. Web Design System", + "URL": "https://github.com/uswds/uswds" + } + ], + "status": "Production", + "tags": [ + "FEC", + "github", + "agency website", + "content" + ], + "vcs": "git" + }, + { + "organization": "Federal Election Commission", + "contact": { + "URL": "https://github.com/fecgov/fec/issues", + "email": "opensource@fec.gov" + }, + "date": { + "created": "2018-07-12T15:14:41+00:00", + "lastModified": "2018-07-12T15:14:38+00:00", + "metadataLastUpdated": "2018-08-03T12:49:57+00:00" + }, + "description": "Reverse proxy app to combine multiple applications.", + "downloadURL": "https://api.github.com/repos/fecgov/fec-proxy/downloads", + "homepageURL": "https://github.com/fecgov/fec-proxy", + "laborHours": 52.0, + "languages": [], + "name": "fec-proxy", + "permissions": { + "usageType": "openSource", + "licenses": [ + { + "URL": "https://github.com/fecgov/fec-proxy/blob/master/LICENSE.md", + "name": "CC0" + } + ] + }, + "repositoryURL": "https://github.com/fecgov/fec-proxy", + "status": "Production", + "tags": [ + "FEC", + "github", + "proxy" + ], + "vcs": "git" + }, + { + "organization": "Federal Election Commission", + "contact": { + "URL": "https://github.com/fecgov/fec/issues", + "email": "opensource@fec.gov" + }, + "date": { + "created": "2018-07-17T17:12:41+00:00", + "lastModified": "2018-07-17T14:46:12+00:00", + "metadataLastUpdated": "2018-08-03T12:49:57+00:00" + + }, + "description": "The Federal Election Commission's web-based application that makes regulations easier to find, read and understand.", + "downloadURL": "https://api.github.com/repos/fecgov/fec-eregs/downloads", + "homepageURL": "https://www.fec.gov/regulations", + "laborHours": 8406.666666666668, + "languages": [ + "CSS", + "HTML", + "Python", + "JavaScript" + ], + "name": "fec-eregs", + "permissions": { + "usageType": "openSource", + "licenses": [ + { + "URL": "https://github.com/fecgov/fec-eregs/blob/master/LICENSE.md", + "name": "CC0" + } + ] + }, + "repositoryURL": "https://github.com/fecgov/fec-eregs", + "relatedCode": [ + { + "name": "regulations-core", + "URL": "https://github.com/eregs/regulations-core" + }, + { + "name": "regulations-site", + "URL": "https://github.com/eregs/regulations-site" + }, + { + "name": "regulations-parser", + "URL": "https://github.com/eregs/regulations-parser" + } + ], + "status": "Production", + "tags": [ + "FEC", + "github", + "regulations", + "search", + "eRegs", + "CFR" + ], + "vcs": "git" + }, + { + "organization": "Federal Election Commission", + "contact": { + "URL": "https://github.com/fecgov/fec/issues", + "email": "opensource@fec.gov" + }, + "date": { + "created": "2018-07-02T22:12:06+00:00", + "lastModified": "2018-06-13T23:12:32+00:00", + "metadataLastUpdated": "2018-08-03T12:49:57+00:00" + }, + "description": "Pattern Library for the new FEC.gov", + "downloadURL": "https://api.github.com/repos/fecgov/fec-pattern-library/downloads", + "homepageURL": "https://fec-pattern-library.app.cloud.gov/", + "laborHours": 11110.666666666666, + "languages": [ + "HTML", + "JavaScript", + "CSS", + "Shell" + ], + "name": "fec-pattern-library", + "permissions": { + "usageType": "openSource", + "licenses": [ + { + "URL": "https://github.com/fecgov/fec-pattern-library/blob/master/LICENSE.md", + "name": "CC0" + } + ] + }, + "repositoryURL": "https://github.com/fecgov/fec-pattern-library", + "relatedCode": [ + { + "name": "U.S. Web Design System", + "URL": "https://github.com/uswds/uswds" + } + ], + "status": "Production", + "tags": [ + "FEC", + "github", + "styles", + "design patterns" + ], + "vcs": "git" + }, + { + "organization": "Federal Election Commission", + "contact": { + "URL": "https://github.com/fecgov/fec/issues", + "email": "opensource@fec.gov" + }, + "date": { + "created": "2018-05-29T23:58:34+00:00", + "lastModified": "2018-05-29T23:58:31+00:00", + "metadataLastUpdated": "2018-08-03T12:49:57+00:00" + }, + "description": "Terraform configuration and CircleCI for managing custom FEC infrastructure in AWS.", + "downloadURL": "https://api.github.com/repos/fecgov/fec-infrastructure/downloads", + "homepageURL": "https://github.com/fecgov/fec-infrastructure", + "laborHours": 17.333333333333336, + "languages": [ + "HCL" + ], + "name": "fec-infrastructure", + "permissions": { + "usageType": "openSource", + "licenses": [ + { + "URL": "https://github.com/fecgov/fec-infrastructure/blob/master/LICENSE.md", + "name": "CC0" + } + ] + }, + "repositoryURL": "https://github.com/fecgov/fec-infrastructure", + "status": "Production", + "tags": [ + "FEC", + "github", + "ferraform", + "configuration", + "AWS" + ], + "vcs": "git" + } + ], + "version": "2.0.0" +} diff --git a/fec/fec/urls.py b/fec/fec/urls.py index 20e588e4b1..dfbd30dadd 100644 --- a/fec/fec/urls.py +++ b/fec/fec/urls.py @@ -28,6 +28,11 @@ url(r'', include('data.urls')), # URLs for /data url(r'', include('legal.urls')), # URLs for legal pages url(r'', include(wagtail_urls)), + url(r'^code\.json$', + TemplateView.as_view( + template_name='code.json' + ) + ) ] if settings.FEC_CMS_ENVIRONMENT != 'LOCAL': From 6fe3c1380b7cb441f07b64becd57a418fbca61a8 Mon Sep 17 00:00:00 2001 From: Andrew Burnes <andrew.burnes@gsa.gov> Date: Fri, 17 Aug 2018 11:57:52 -0300 Subject: [PATCH 24/49] Update proptypes validation for legal app --- fec/fec/static/js/draftail/Anchor.js | 29 +++++++++---------- fec/fec/static/js/draftail/Glossary.js | 29 +++++++++---------- fec/fec/static/js/draftail/SansSerif.js | 29 +++++++++---------- .../static/js/draftail/components/Modal.js | 11 +++++++ fec/fec/static/js/legal/FilterPanel.js | 7 +++-- fec/fec/static/js/legal/SearchResults.js | 7 +++++ fec/fec/static/js/legal/Tags.js | 7 +++++ fec/fec/static/js/legal/filters/Checkbox.js | 15 ++++++++++ .../static/js/legal/filters/CheckboxFilter.js | 4 +-- .../static/js/legal/filters/CheckboxList.js | 4 +-- .../static/js/legal/filters/CitationFilter.js | 3 +- .../legal/filters/CitationRequireAllRadio.js | 2 ++ fec/fec/static/js/legal/filters/DateFilter.js | 21 ++++++++++++++ fec/fec/static/js/legal/filters/Dropdown.js | 4 +-- fec/fec/static/js/legal/filters/TextFilter.js | 5 ++-- 15 files changed, 118 insertions(+), 59 deletions(-) diff --git a/fec/fec/static/js/draftail/Anchor.js b/fec/fec/static/js/draftail/Anchor.js index d956f7c85e..05b89c81bc 100644 --- a/fec/fec/static/js/draftail/Anchor.js +++ b/fec/fec/static/js/draftail/Anchor.js @@ -53,18 +53,15 @@ class AnchorSource extends React.Component { } } -AnchorSource.defaultProps = { - editorState: {}, - entityType: {}, - onComplete: {}, - children: {} -}; - AnchorSource.propTypes = { - editorState: PropTypes.oneOfType(PropTypes.object, PropTypes.func), - entityType: PropTypes.oneOfType(PropTypes.object, PropTypes.func), - onComplete: PropTypes.oneOfType(PropTypes.object, PropTypes.func), - children: PropTypes.oneOfType(PropTypes.object, PropTypes.func) + children: PropTypes.oneOfType([ + PropTypes.array, + PropTypes.object, + PropTypes.func + ]), + editorState: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), + entityType: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), + onComplete: PropTypes.oneOfType([PropTypes.object, PropTypes.func]) }; const Anchor = ({ children }) => ( @@ -77,12 +74,12 @@ const Anchor = ({ children }) => ( </span> ); -Anchor.defaultProps = { - children: {} -}; - Anchor.propTypes = { - children: PropTypes.oneOfType(PropTypes.object, PropTypes.func) + children: PropTypes.oneOfType([ + PropTypes.array, + PropTypes.object, + PropTypes.func + ]) }; module.exports = { diff --git a/fec/fec/static/js/draftail/Glossary.js b/fec/fec/static/js/draftail/Glossary.js index 592a03b764..ffe48e2334 100644 --- a/fec/fec/static/js/draftail/Glossary.js +++ b/fec/fec/static/js/draftail/Glossary.js @@ -119,28 +119,25 @@ class GlossarySource extends React.Component { } } -GlossarySource.defaultProps = { - editorState: {}, - entityType: {}, - onComplete: {}, - children: {} -}; - GlossarySource.propTypes = { - editorState: PropTypes.oneOfType(PropTypes.object, PropTypes.func), - entityType: PropTypes.oneOfType(PropTypes.object, PropTypes.func), - onComplete: PropTypes.oneOfType(PropTypes.object, PropTypes.func), - children: PropTypes.oneOfType(PropTypes.object, PropTypes.func) + children: PropTypes.oneOfType([ + PropTypes.array, + PropTypes.object, + PropTypes.func + ]), + editorState: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), + entityType: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), + onComplete: PropTypes.oneOfType([PropTypes.object, PropTypes.func]) }; const Glossary = ({ children }) => <b className="term">{children}</b>; -Glossary.defaultProps = { - children: {} -}; - Glossary.propTypes = { - children: PropTypes.oneOfType(PropTypes.object, PropTypes.func) + children: PropTypes.oneOfType([ + PropTypes.array, + PropTypes.object, + PropTypes.func + ]) }; module.exports = { diff --git a/fec/fec/static/js/draftail/SansSerif.js b/fec/fec/static/js/draftail/SansSerif.js index c7d63ab144..ac3ca0d4f5 100644 --- a/fec/fec/static/js/draftail/SansSerif.js +++ b/fec/fec/static/js/draftail/SansSerif.js @@ -52,28 +52,25 @@ class SansserifSource extends React.Component { } } -SansserifSource.defaultProps = { - editorState: {}, - entityType: {}, - onComplete: {}, - children: {} -}; - SansserifSource.propTypes = { - editorState: PropTypes.oneOfType(PropTypes.object, PropTypes.func), - entityType: PropTypes.oneOfType(PropTypes.object, PropTypes.func), - onComplete: PropTypes.oneOfType(PropTypes.object, PropTypes.func), - children: PropTypes.oneOfType(PropTypes.object, PropTypes.func) + children: PropTypes.oneOfType([ + PropTypes.array, + PropTypes.object, + PropTypes.func + ]), + editorState: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), + entityType: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), + onComplete: PropTypes.oneOfType([PropTypes.object, PropTypes.func]) }; const Sansserif = ({ children }) => <span className="t-sans">{children}</span>; -Sansserif.defaultTypes = { - children: {} -}; - Sansserif.propTypes = { - children: PropTypes.oneOfType(PropTypes.object, PropTypes.func) + children: PropTypes.oneOfType([ + PropTypes.array, + PropTypes.object, + PropTypes.func + ]) }; module.exports = { diff --git a/fec/fec/static/js/draftail/components/Modal.js b/fec/fec/static/js/draftail/components/Modal.js index a13c7e71c4..f9b414e604 100644 --- a/fec/fec/static/js/draftail/components/Modal.js +++ b/fec/fec/static/js/draftail/components/Modal.js @@ -1,4 +1,5 @@ import React from 'react'; +import PropTypes from 'prop-types'; const Cancel = ({ handleClose }) => ( <div @@ -70,4 +71,14 @@ Modal.defaultProps = { title: '' }; +Modal.propTypes = { + children: PropTypes.oneOfType([ + PropTypes.array, + PropTypes.object, + PropTypes.func + ]), + handleClose: PropTypes.func, + title: PropTypes.string +}; + export default Modal; diff --git a/fec/fec/static/js/legal/FilterPanel.js b/fec/fec/static/js/legal/FilterPanel.js index 6a21a0d786..6d005671d1 100644 --- a/fec/fec/static/js/legal/FilterPanel.js +++ b/fec/fec/static/js/legal/FilterPanel.js @@ -36,14 +36,17 @@ class FilterPanel extends React.Component { } FilterPanel.propTypes = { - children: {}, header: '', id: '', startOpen: true }; FilterPanel.propTypes = { - children: PropTypes.object, + children: PropTypes.oneOfType([ + PropTypes.array, + PropTypes.object, + PropTypes.func + ]), header: PropTypes.string, id: PropTypes.string, startOpen: PropTypes.bool diff --git a/fec/fec/static/js/legal/SearchResults.js b/fec/fec/static/js/legal/SearchResults.js index c199cab932..e8f734d44e 100644 --- a/fec/fec/static/js/legal/SearchResults.js +++ b/fec/fec/static/js/legal/SearchResults.js @@ -1,4 +1,5 @@ const React = require('react'); +const PropTypes = require('prop-types'); const $ = require('jquery'); const moment = require('moment'); @@ -162,4 +163,10 @@ function SearchResults(props) { } } +SearchResults.propTypes = { + advisory_opinion: PropTypes.array, + loading: PropTypes.string, + query: PropTypes.string +}; + module.exports = SearchResults; diff --git a/fec/fec/static/js/legal/Tags.js b/fec/fec/static/js/legal/Tags.js index dfdcd17095..b797ed111b 100644 --- a/fec/fec/static/js/legal/Tags.js +++ b/fec/fec/static/js/legal/Tags.js @@ -1,4 +1,5 @@ const React = require('react'); +const PropTypes = require('prop-types'); const _ = require('underscore'); const requestorOptions = require('./requestorOptions'); const documentTypes = require('./documentTypes'); @@ -162,4 +163,10 @@ function Tags(props) { } } +Tags.propTypes = { + handleRemove: PropTypes.func, + query: PropTypes.object, + resultCount: PropTypes.number +}; + module.exports = Tags; diff --git a/fec/fec/static/js/legal/filters/Checkbox.js b/fec/fec/static/js/legal/filters/Checkbox.js index 0a487ac6cd..10b4c58971 100644 --- a/fec/fec/static/js/legal/filters/Checkbox.js +++ b/fec/fec/static/js/legal/filters/Checkbox.js @@ -1,4 +1,5 @@ const React = require('react'); +const PropTypes = require('prop-types'); function Checkbox(props) { return ( @@ -15,4 +16,18 @@ function Checkbox(props) { ); } +Checkbox.defaultProps = { + checked: false, + handleChange: function() {}, + label: 'label', + name: 'name' +}; + +Checkbox.propTypes = { + checked: PropTypes.bool, + handleChange: PropTypes.func, + label: PropTypes.string, + name: PropTypes.string +}; + module.exports = Checkbox; diff --git a/fec/fec/static/js/legal/filters/CheckboxFilter.js b/fec/fec/static/js/legal/filters/CheckboxFilter.js index 0f05cb7683..71a90c1b1c 100644 --- a/fec/fec/static/js/legal/filters/CheckboxFilter.js +++ b/fec/fec/static/js/legal/filters/CheckboxFilter.js @@ -18,8 +18,8 @@ function CheckboxFilter(props) { CheckboxFilter.propTypes = { checked: false, handleChange: function() {}, - label: '', - name: '' + label: 'label', + name: 'name' }; CheckboxFilter.propTypes = { diff --git a/fec/fec/static/js/legal/filters/CheckboxList.js b/fec/fec/static/js/legal/filters/CheckboxList.js index f5d954106d..a91bbc8ebf 100644 --- a/fec/fec/static/js/legal/filters/CheckboxList.js +++ b/fec/fec/static/js/legal/filters/CheckboxList.js @@ -46,8 +46,8 @@ function CheckboxList(props) { CheckboxList.propTypes = { handleChange: function() {}, - label: '', - name: '', + label: 'label', + name: 'name', options: [], value: '' }; diff --git a/fec/fec/static/js/legal/filters/CitationFilter.js b/fec/fec/static/js/legal/filters/CitationFilter.js index fe1d4af3fe..18708584c7 100644 --- a/fec/fec/static/js/legal/filters/CitationFilter.js +++ b/fec/fec/static/js/legal/filters/CitationFilter.js @@ -219,11 +219,12 @@ class CitationFilter extends React.Component { CitationFilter.defaultProps = { lastFilter: '', - name: '', + name: 'name', value: [] }; CitationFilter.propTypes = { + citationType: PropTypes.string, lastFilter: PropTypes.string, name: PropTypes.string, value: PropTypes.array diff --git a/fec/fec/static/js/legal/filters/CitationRequireAllRadio.js b/fec/fec/static/js/legal/filters/CitationRequireAllRadio.js index 1bca79de21..d60eb8cf21 100644 --- a/fec/fec/static/js/legal/filters/CitationRequireAllRadio.js +++ b/fec/fec/static/js/legal/filters/CitationRequireAllRadio.js @@ -37,6 +37,8 @@ CitationRequireAllRadio.defaultProps = { }; CitationRequireAllRadio.propTypes = { + handleChange: PropTypes.func, + name: PropTypes.string, value: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]) }; diff --git a/fec/fec/static/js/legal/filters/DateFilter.js b/fec/fec/static/js/legal/filters/DateFilter.js index 4352ac1f39..794ed500bc 100644 --- a/fec/fec/static/js/legal/filters/DateFilter.js +++ b/fec/fec/static/js/legal/filters/DateFilter.js @@ -1,4 +1,5 @@ const React = require('react'); +const PropTypes = require('prop-types'); const InputElement = require('react-input-mask'); function DateFilter(props) { @@ -47,4 +48,24 @@ function DateFilter(props) { ); } +DateFilter.defaultProps = { + instantQuery: function() {}, + label: 'label', + max_name: 'max_name', + max_value: '', + min_name: 'min_name', + min_value: '', + setQuery: function() {} +}; + +DateFilter.propTypes = { + instantQuery: PropTypes.func, + label: PropTypes.string, + max_name: PropTypes.string, + max_value: PropTypes.string, + min_name: PropTypes.string, + min_value: PropTypes.string, + setQuery: PropTypes.func +}; + module.exports = DateFilter; diff --git a/fec/fec/static/js/legal/filters/Dropdown.js b/fec/fec/static/js/legal/filters/Dropdown.js index 7814f18bfc..a7885b16e7 100644 --- a/fec/fec/static/js/legal/filters/Dropdown.js +++ b/fec/fec/static/js/legal/filters/Dropdown.js @@ -25,8 +25,8 @@ function Dropdown(props) { Dropdown.defaultProps = { handleChange: function() {}, - label: '', - name: '', + label: 'label', + name: 'name', options: [], value: '' }; diff --git a/fec/fec/static/js/legal/filters/TextFilter.js b/fec/fec/static/js/legal/filters/TextFilter.js index d98dafceb5..2839f37984 100644 --- a/fec/fec/static/js/legal/filters/TextFilter.js +++ b/fec/fec/static/js/legal/filters/TextFilter.js @@ -1,4 +1,5 @@ const React = require('react'); +const PropTypes = require('prop-types'); function TextFilter(props) { function handleKeydown(e) { @@ -48,9 +49,9 @@ function TextFilter(props) { TextFilter.defaultProps = { getResults: function() {}, handleChange: function() {}, - helpText: '', + helpText: 'help', keywordModal: true, - name: '', + name: 'name', value: '' }; From c08b8e82604de20418d746b4f94e9756ae45006a Mon Sep 17 00:00:00 2001 From: john carroll <jcarroll@fec.gov> Date: Fri, 17 Aug 2018 11:49:52 -0400 Subject: [PATCH 25/49] changed laguage --- fec/checkweb/templates/partials/api-info.jinja | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fec/checkweb/templates/partials/api-info.jinja b/fec/checkweb/templates/partials/api-info.jinja index 2fd424ed5b..69614fced5 100644 --- a/fec/checkweb/templates/partials/api-info.jinja +++ b/fec/checkweb/templates/partials/api-info.jinja @@ -3,7 +3,7 @@ <div class="slab slab--inline slab--neutral u-padding--left u-padding--right"> <div id="api-info" class="entity__figure row"> <div class="heading--section heading--with-action u-no-margin"> - <h3 class="heading__left">api ==> {{ api_name }}</h3> + <h3 class="heading__left">Your API is:  {{ api_name }}</h3> </div> </div> </div> From 8d16225241535ad0a342083d9ace02dcc88bb87a Mon Sep 17 00:00:00 2001 From: Pat Phongsvirajati <pphongsvirajati@fec.gov> Date: Fri, 17 Aug 2018 12:58:37 -0400 Subject: [PATCH 26/49] Revert "New app checkweb to check website status and get some information" --- fec/checkweb/__init__.py | 0 fec/checkweb/admin.py | 3 -- fec/checkweb/apps.py | 5 -- fec/checkweb/templates/checkweb.jinja | 45 ---------------- .../macros/page-header-checkweb.jinja | 11 ---- .../templates/partials/api-info.jinja | 10 ---- .../templates/partials/check-export.jinja | 54 ------------------- .../partials/checkweb-sidebar-nav.jinja | 20 ------- .../partials/election-candidate-totals.jinja | 41 -------------- fec/checkweb/tests.py | 3 -- fec/checkweb/urls.py | 7 --- fec/checkweb/views.py | 17 ------ fec/fec/settings/base.py | 47 +++++----------- fec/fec/urls.py | 30 +++-------- 14 files changed, 21 insertions(+), 272 deletions(-) delete mode 100644 fec/checkweb/__init__.py delete mode 100644 fec/checkweb/admin.py delete mode 100644 fec/checkweb/apps.py delete mode 100644 fec/checkweb/templates/checkweb.jinja delete mode 100644 fec/checkweb/templates/macros/page-header-checkweb.jinja delete mode 100644 fec/checkweb/templates/partials/api-info.jinja delete mode 100644 fec/checkweb/templates/partials/check-export.jinja delete mode 100644 fec/checkweb/templates/partials/checkweb-sidebar-nav.jinja delete mode 100644 fec/checkweb/templates/partials/election-candidate-totals.jinja delete mode 100644 fec/checkweb/tests.py delete mode 100644 fec/checkweb/urls.py delete mode 100644 fec/checkweb/views.py diff --git a/fec/checkweb/__init__.py b/fec/checkweb/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/fec/checkweb/admin.py b/fec/checkweb/admin.py deleted file mode 100644 index 8c38f3f3da..0000000000 --- a/fec/checkweb/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. diff --git a/fec/checkweb/apps.py b/fec/checkweb/apps.py deleted file mode 100644 index 39c479e0b9..0000000000 --- a/fec/checkweb/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.apps import AppConfig - - -class TestinfoConfig(AppConfig): - name = 'checkweb' diff --git a/fec/checkweb/templates/checkweb.jinja b/fec/checkweb/templates/checkweb.jinja deleted file mode 100644 index 58f987339b..0000000000 --- a/fec/checkweb/templates/checkweb.jinja +++ /dev/null @@ -1,45 +0,0 @@ -{% extends 'layouts/main.jinja' %} -{% import 'macros/page-header-checkweb.jinja' as header %} - -{% block title %}{{ title }}{% endblock %} - -{% block css %} - <link rel="stylesheet" type="text/css" href="{{ asset_for_css('elections.css') }}" /> -{% endblock %} - -{% block body %} - {{ header.header('check web') }} - <div class="u-padding--left u-padding--right tab-interface"> - <header class="main"> - <h1 class="entity__name">Check FEC website</h1> - <div class="heading--section"> - <span class="t-data t-bold"></span> - </div> - </header> - <div> - {% include 'partials/checkweb-sidebar-nav.jinja' %} - <div class="main__content--right-full"> - <section id="section-1" role="tabpanel" aria-hidden="true" aria-labelledby="section-1-heading"> - {% include 'partials/election-candidate-totals.jinja' %} - {% include 'partials/api-info.jinja' %} - </section> - </div> - </div> - </div> -{% endblock %} - -{% block scripts %} -<script> -var context = { - election: { - cycle: '{{ cycle }}', - office: '{{ office }}', - state: '{{ state or '' }}', - stateFull: '{{ state_full or '' }}', - district: '{{ district or '' }}' - } -}; -</script> -<script src="{{ asset_for_js('dataviz-common.js') }}"></script> -<script src="{{ asset_for_js('elections.js') }}"></script> -{% endblock %} diff --git a/fec/checkweb/templates/macros/page-header-checkweb.jinja b/fec/checkweb/templates/macros/page-header-checkweb.jinja deleted file mode 100644 index 728b2455e3..0000000000 --- a/fec/checkweb/templates/macros/page-header-checkweb.jinja +++ /dev/null @@ -1,11 +0,0 @@ -{% macro header(title) %} -<div class="page-header page-header--primary"> - <ul class="breadcrumbs"> - <li class="breadcrumbs__item"><a href="/" class="breadcrumbs__link" rel="Home">Home</a></li> - <li class="breadcrumbs__item breadcrumbs__item--current"> - <span class="breadcrumbs__separator">›</span> - {{ title }} - </li> - </ul> -</div> -{% endmacro %} diff --git a/fec/checkweb/templates/partials/api-info.jinja b/fec/checkweb/templates/partials/api-info.jinja deleted file mode 100644 index 69614fced5..0000000000 --- a/fec/checkweb/templates/partials/api-info.jinja +++ /dev/null @@ -1,10 +0,0 @@ -<div class='container'> - <h2 id="section-1-heading">(2)Check API information</h2> - <div class="slab slab--inline slab--neutral u-padding--left u-padding--right"> - <div id="api-info" class="entity__figure row"> - <div class="heading--section heading--with-action u-no-margin"> - <h3 class="heading__left">Your API is:  {{ api_name }}</h3> - </div> - </div> - </div> -</div> diff --git a/fec/checkweb/templates/partials/check-export.jinja b/fec/checkweb/templates/partials/check-export.jinja deleted file mode 100644 index 3d8dcc7ba4..0000000000 --- a/fec/checkweb/templates/partials/check-export.jinja +++ /dev/null @@ -1,54 +0,0 @@ -<section id="section-2" role="tabpanel" aria-hidden="true" aria-labelledby="section-2-heading"> - <div class="container"> - <h2 id="section-2-heading">About this election</h2> - <div class="entity__figure u-no-padding row election-dates"> - </div> - {% include 'partials/elections/election-profile-cards.jinja' %} - <div class="slab slab--inline slab--neutral u-padding--left u-padding--right"> - <div id="candidate-information" class="entity__figure row"> - <div class="heading--section heading--with-action u-no-margin"> - <h3 class="heading__left">Candidate information</h3> - <div class="heading__right"> - <span class="is-incumbent t-sans t-note" style="padding-right: 1em">Incumbent</span> - <div - class="message message--info message--mini t-left-aligned data-container__message" - data-export-message-for="candidate-information" - aria-hidden="false" - > - Exports coming soon. - </div> - <button - type="button" - class="js-export button button--cta button--export" - data-export-for="candidate-information" - > - Export - </button> - </div> - </div> - <table - id="candidate-information-results" - class="data-table data-table--heading-borders scrollX" - data-type="candidate-information" - > - <thead> - <th role="col">Name</th> - <th role="col">Party</th> - <th role="col">Principal campaign committee</th> - </thead> - </table> - </div> - <div id="state-election-offices" class="entity__figure row"> - <div class="heading--section heading--with-action"> - <h3 class="heading__left">State election offices</h3> - </div> - {% if office == 'president' %} - <p>Ballot access procedures for presidential candidates vary by state. Contact each <a href="https://transition.fec.gov/pubrec/stateelection.shtml">state election office</a> for information.</p> - {% else %} - <p>State laws and procedures govern elections for state or local offices as well as how candidates come to appear on election ballots. Contact the appropriate state election office for more information.</p> - <div id="election-offices"></div> - {% endif %} - </div> - </div> - </div> -</section> diff --git a/fec/checkweb/templates/partials/checkweb-sidebar-nav.jinja b/fec/checkweb/templates/partials/checkweb-sidebar-nav.jinja deleted file mode 100644 index 33c5e725fa..0000000000 --- a/fec/checkweb/templates/partials/checkweb-sidebar-nav.jinja +++ /dev/null @@ -1,20 +0,0 @@ -<nav class="sidebar side-nav-alt"> - <ul class="tablist" role="tablist" data-name="tab"> - <li class="side-nav__item" role="presentation"> - <a - class="side-nav__link" - role="tab" - data-name="election-data" - tabindex="0" - aria-controls="panel1" - href="#section-1" - > - Check FEC website - </a> - <ul> - <li><a href="#candidate-financial-totals">(1)Check export function</a></li> - <li><a href="#api-info">(2)Check API info</a></li> - </ul> - </li> - </ul> -</nav> \ No newline at end of file diff --git a/fec/checkweb/templates/partials/election-candidate-totals.jinja b/fec/checkweb/templates/partials/election-candidate-totals.jinja deleted file mode 100644 index 62a4db1e0d..0000000000 --- a/fec/checkweb/templates/partials/election-candidate-totals.jinja +++ /dev/null @@ -1,41 +0,0 @@ -<div class='container'> - <h2 id="section-1-heading">(1)Check download/export function</h2> - <div class="slab slab--inline slab--neutral u-padding--left u-padding--right"> - - <div id="candidate-financial-totals" class="entity__figure row"> - <div class="heading--section heading--with-action u-no-margin"> - <h3 class="heading__left">Candidate financial totals ({{cycle}}--{{state}}--{{office}})</h3> - <div class="heading__right"> - <span class="is-incumbent t-sans t-note" style="padding-right: 1em">Incumbent</span> - <div - class="message message--info message--mini t-left-aligned data-container__message" - data-export-message-for="candidate-financial-totals" - aria-hidden="false" - > - Exports coming soon. - </div> - <button - type="button" - class="js-export button button--cta button--export" - data-export-for="candidate-financial-totals" - > - Export - </button> - </div> - </div> - <table - class="data-table data-table--heading-borders scrollX" - data-type="candidate-financial-totals" - > - <thead> - <th role="col">Candidate</th> - <th role="col">Party</th> - <th role="col">Total receipts</th> - <th role="col">Total disbursements</th> - <th role="col">Cash on hand</th> - <th role="col">Source reports</th> - </thead> - </table> - </div> - </div> -</div> diff --git a/fec/checkweb/tests.py b/fec/checkweb/tests.py deleted file mode 100644 index 7ce503c2dd..0000000000 --- a/fec/checkweb/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/fec/checkweb/urls.py b/fec/checkweb/urls.py deleted file mode 100644 index 469bb85fc8..0000000000 --- a/fec/checkweb/urls.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.conf.urls import url - -from checkweb import views - -urlpatterns = [ - url(r'^checkweb/$', views.checkweb), -] diff --git a/fec/checkweb/views.py b/fec/checkweb/views.py deleted file mode 100644 index 4bc62aff8a..0000000000 --- a/fec/checkweb/views.py +++ /dev/null @@ -1,17 +0,0 @@ -from django.shortcuts import render -from data import constants -from django.conf import settings - - -def checkweb(request, office='senate', cycle=2018, state='VA', district=None): - return render(request, 'checkweb.jinja', { - 'office': office, - 'office_code': office[0], - 'parent': 'checkweb', - 'cycle': int(cycle), - 'state': state, - 'state_full': constants.states[state.upper()] if state else None, - 'district': district, - 'title': 'check website', - 'api_name': settings.FEC_API_URL, - }) diff --git a/fec/fec/settings/base.py b/fec/fec/settings/base.py index 8237d81eed..c815362b54 100644 --- a/fec/fec/settings/base.py +++ b/fec/fec/settings/base.py @@ -32,22 +32,13 @@ FEC_SERVICE_NOW_USERNAME = env.get_credential('FEC_SERVICE_NOW_USERNAME') FEC_SERVICE_NOW_PASSWORD = env.get_credential('FEC_SERVICE_NOW_PASSWORD') FEC_DIGITALGOV_KEY = env.get_credential('FEC_DIGITALGOV_KEY') -FEC_DIGITALGOV_DRAWER_KEY_MAIN = env.get_credential( - 'DIGITALGOV_DRAWER_KEY_MAIN', '' -) -FEC_DIGITALGOV_DRAWER_KEY_TRANSITION = env.get_credential( - 'DIGITALGOV_DRAWER_KEY_TRANSITION', '' -) +FEC_DIGITALGOV_DRAWER_KEY_MAIN = env.get_credential('DIGITALGOV_DRAWER_KEY_MAIN', '') +FEC_DIGITALGOV_DRAWER_KEY_TRANSITION = env.get_credential('DIGITALGOV_DRAWER_KEY_TRANSITION', '') DIGITALGOV_BASE_API_URL = 'https://i14y.usa.gov/api/v1' DIGITALGOV_DRAWER_HANDLE = 'main' -FEC_TRANSITION_URL = env.get_credential( - 'FEC_TRANSITION_URL', 'https://transition.fec.gov' -) -FEC_CLASSIC_URL = env.get_credential( - 'FEC_CLASSIC_URL', - 'http://classic.fec.gov' -) +FEC_TRANSITION_URL = env.get_credential('FEC_TRANSITION_URL', 'https://transition.fec.gov') +FEC_CLASSIC_URL = env.get_credential('FEC_CLASSIC_URL', 'http://classic.fec.gov') FEATURES = { 'record': bool(env.get_credential('FEC_FEATURE_RECORD', '')), @@ -64,10 +55,7 @@ 'prod': 'PRODUCTION', 'feature': 'FEATURE', } -FEC_CMS_ENVIRONMENT = ENVIRONMENTS.get( - env.get_credential('FEC_CMS_ENVIRONMENT'), - 'LOCAL' -) +FEC_CMS_ENVIRONMENT = ENVIRONMENTS.get(env.get_credential('FEC_CMS_ENVIRONMENT'), 'LOCAL') CONTACT_EMAIL = 'webmanager@fec.gov' WEBMANAGER_EMAIL = "webmanager@fec.gov" @@ -109,7 +97,6 @@ 'home', 'data', 'legal', - 'checkweb', 'uaa_client', 'extend_admin', ) @@ -136,6 +123,7 @@ from data import constants + TEMPLATES = [ { 'BACKEND': 'django_jinja.backend.Jinja2', @@ -275,19 +263,12 @@ AWS_LOCATION = 'cms-content' UAA_CLIENT_ID = env.get_credential('CMS_LOGIN_CLIENT_ID', 'my-client-id') -UAA_CLIENT_SECRET = env.get_credential( - 'CMS_LOGIN_CLIENT_SECRET', - 'my-client-secret' -) -# fake uaa server deploys locally on port 8080. -# Will be needed to login for local use -# TODO: These will have to have a explicit reference -# until we can figure out how -# to silence django warnings about the url being http (it expects https). -# UAA_AUTH_URL = env.get_credential('CMS_LOGIN_AUTH_URL', -# 'http://localhost:8080/oauth/authorize') -# UAA_TOKEN_URL = env.get_credential('CMS_LOGIN_TOKEN_URL', -# 'http://localhost:8080/oauth/token') +UAA_CLIENT_SECRET = env.get_credential('CMS_LOGIN_CLIENT_SECRET', 'my-client-secret') +#fake uaa server deploys locally on port 8080. Will be needed to login for local use +#TODO: These will have to have a explicit reference until we can figure out how +#to silence django warnings about the url being http (it expects https). +#UAA_AUTH_URL = env.get_credential('CMS_LOGIN_AUTH_URL', 'http://localhost:8080/oauth/authorize') +#UAA_TOKEN_URL = env.get_credential('CMS_LOGIN_TOKEN_URL','http://localhost:8080/oauth/token') UAA_AUTH_URL = 'https://login.fr.cloud.gov/oauth/authorize' UAA_TOKEN_URL = 'https://login.fr.cloud.gov/oauth/token' WAGTAIL_FRONTEND_LOGIN_URL = 'uaa_client:login' @@ -296,9 +277,7 @@ ['django.contrib.auth.backends.ModelBackend', 'uaa_client.authentication.UaaBackend'] -DEFAULT_AUTHENTICATION_CLASSES = [ - 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', -] +DEFAULT_AUTHENTICATION_CLASSES = ['rest_framework_jwt.authentication.JSONWebTokenAuthentication',] LOGGING = { 'version': 1, diff --git a/fec/fec/urls.py b/fec/fec/urls.py index 44d88ebb4a..20e588e4b1 100644 --- a/fec/fec/urls.py +++ b/fec/fec/urls.py @@ -14,37 +14,25 @@ urlpatterns = [ - url( - r'^documents/(\d+)/(.*)$', - home_views.serve_wagtail_doc, - name='wagtaildocs_serve' - ), + url(r'^documents/(\d+)/(.*)$', home_views.serve_wagtail_doc, name='wagtaildocs_serve'), url(r'^auth/', include(uaa_urls)), url(r'^admin/', include(wagtailadmin_urls)), url(r'^calendar/$', home_views.calendar), - url( - r'^about/leadership-and-structure/commissioners/$', - home_views.commissioners - ), + url(r'^about/leadership-and-structure/commissioners/$', home_views.commissioners), url(r'^documents/', include(wagtaildocs_urls)), - url( - r'^help-candidates-and-committees/question-rad/$', - home_views.contact_rad - ), + url(r'^help-candidates-and-committees/question-rad/$', home_views.contact_rad), url(r'^help-candidates-and-committees/guides/$', home_views.guides), url(r'^meetings/$', home_views.index_meetings, name="meetings_page"), url(r'^search/$', search_views.search, name='search'), url(r'^updates/$', home_views.updates), url(r'', include('data.urls')), # URLs for /data url(r'', include('legal.urls')), # URLs for legal pages - url(r'', include('checkweb.urls')), # URL for check web url(r'', include(wagtail_urls)), ] if settings.FEC_CMS_ENVIRONMENT != 'LOCAL': - # admin/login always must come before admin/, - # so place at beginning of list - urlpatterns.insert(0, url(r'^admin/login', uaa_views.login, name='login')) + #admin/login always must come before admin/, so place at beginning of list + urlpatterns.insert(0,url(r'^admin/login', uaa_views.login, name='login')) if settings.FEC_CMS_ROBOTS: urlpatterns += url( @@ -62,9 +50,7 @@ # Serve static and media files from development server urlpatterns += staticfiles_urlpatterns() - urlpatterns += static( - settings.MEDIA_URL, document_root=settings.MEDIA_ROOT - ) + urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) - # hide django-admin unless DEBUG=True - urlpatterns.insert(1, url(r'^django-admin/', include(admin.site.urls))) + #hide django-admin unless DEBUG=True + urlpatterns.insert(1,url(r'^django-admin/', include(admin.site.urls))) From e462eeaa36308df41df1ad72770f1e9252602927 Mon Sep 17 00:00:00 2001 From: Laura Beaufort <31420082+lbeaufort@users.noreply.github.com> Date: Tue, 21 Aug 2018 15:45:15 -0400 Subject: [PATCH 27/49] Assign transaction_end at top of template - transaction_end wasn't getting assigned for committee types 'C', 'E', and 'I', resulting in an error --- fec/data/templates/partials/committee/raising.jinja | 13 ++++++++----- .../templates/partials/committee/spending.jinja | 13 ++++++++----- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/fec/data/templates/partials/committee/raising.jinja b/fec/data/templates/partials/committee/raising.jinja index 78f8c51599..67dd9727f0 100644 --- a/fec/data/templates/partials/committee/raising.jinja +++ b/fec/data/templates/partials/committee/raising.jinja @@ -2,16 +2,19 @@ {% import 'macros/disclaimer.jinja' as disclaimer %} {% import 'macros/cycle-select.jinja' as select %} +{% if totals %} + {% if totals.0.transaction_coverage_date is not none %} + {% set transaction_end = totals.0.transaction_coverage_date %} + {% else %} + {% set transaction_end = totals.0.coverage_end_date %} + {% endif %} +{% endif %} + <section id="section-3" role="tabpanel" aria-hidden="true" aria-labelledby="section-3-heading"> <h2 id="section-3-heading">Raising</h2> <div class="slab slab--inline slab--neutral u-padding--left u-padding--right"> {{ select.committee_cycle_select(cycles, cycle, 'receipts')}} {% if totals and committee_type not in ['C', 'E', 'I'] %} - {% if totals.0.transaction_coverage_date is not none %} - {% set transaction_end = totals.0.transaction_coverage_date %} - {% else %} - {% set transaction_end = totals.0.coverage_end_date %} - {% endif %} <div id="total-receipts" class="entity__figure row"> <div class="heading--section heading--with-action"> <h3 class="heading__left">Total receipts</h3> diff --git a/fec/data/templates/partials/committee/spending.jinja b/fec/data/templates/partials/committee/spending.jinja index 213e9435dd..2ab43efceb 100644 --- a/fec/data/templates/partials/committee/spending.jinja +++ b/fec/data/templates/partials/committee/spending.jinja @@ -1,17 +1,20 @@ {% import 'macros/disclaimer.jinja' as disclaimer %} {% import 'macros/cycle-select.jinja' as select %} +{% if totals %} + {% if totals.0.transaction_coverage_date is not none %} + {% set transaction_end = totals.0.transaction_coverage_date %} + {% else %} + {% set transaction_end = totals.0.coverage_end_date %} + {% endif %} +{% endif %} + <section id="section-4" role="tabpanel" aria-hidden="true" aria-labelledby="section-4-heading"> <h2 id="section-4-heading">Spending</h2> <div class="slab slab--inline slab--neutral u-padding--left u-padding--right"> {{ select.committee_cycle_select(cycles, cycle, 'disbursements')}} {% if totals and committee_type not in ['C', 'E', 'I'] %} - {% if totals.0.transaction_coverage_date is not none %} - {% set transaction_end = totals.0.transaction_coverage_date %} - {% else %} - {% set transaction_end = totals.0.coverage_end_date %} - {% endif %} <div id="total-disbursements" class="entity__figure row"> <div class="content__section"> <div class="heading--section heading--with-action"> From 410e9e1e14c4ca2585f521eb791885c71edfc6ff Mon Sep 17 00:00:00 2001 From: Vraj Mohan <radhakrishnan.vrajmohan@gsa.gov> Date: Wed, 22 Aug 2018 12:08:32 -0700 Subject: [PATCH 28/49] Restore test lost in the big merge This test appears to have been missed when openFEC-webapp and fec-cms were merged. --- fec/legal/tests.py | 55 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/fec/legal/tests.py b/fec/legal/tests.py index 7ce503c2dd..04d94879a6 100644 --- a/fec/legal/tests.py +++ b/fec/legal/tests.py @@ -1,3 +1,54 @@ -from django.test import TestCase +import unittest +from collections import OrderedDict +from unittest import mock -# Create your tests here. +from data import api_caller + + +class TestLegal(unittest.TestCase): + @mock.patch.object(api_caller, '_call_api') + def test_load_legal_mur(self, call_api): + call_api.return_value = { + 'docs': [{ + 'no': '1234', + 'mur_type': 'current', + 'participants': [ + { + 'role': 'Complainant', + 'name': 'Gollum', + 'citations': [] + }, + { + 'role': 'Respondent', + 'name': 'Bilbo Baggins', + 'citations': [] + }, + ], + 'commission_votes': [], + 'dispositions': [ + { + 'disposition': 'Conciliation-PC', + 'penalty': 100.0 + }, + { + 'disposition': 'Conciliation-PC', + 'penalty': 0.0 + }, + ], + 'documents': [] + }] + } + + mur = api_caller.load_legal_mur('1234') + + assert mur.get('no') == '1234' + assert mur['participants_by_type'] == OrderedDict([ + ('Respondent', ['Bilbo Baggins']), + ('Complainant', ['Gollum']), + ]) + assert mur['collated_dispositions'] == OrderedDict([ + ('Conciliation-PC', OrderedDict([ + (100.0, [{'penalty': 100.0, 'disposition': 'Conciliation-PC'}]), + (0.0, [{'penalty': 0.0, 'disposition': 'Conciliation-PC'}]) + ])) + ]) From 87660b4052596c13a4576be78ab71d099b2761bf Mon Sep 17 00:00:00 2001 From: Vraj Mohan <radhakrishnan.vrajmohan@gsa.gov> Date: Wed, 22 Aug 2018 12:53:32 -0700 Subject: [PATCH 29/49] Extract get_candidate as a separate method --- fec/data/views.py | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/fec/data/views.py b/fec/data/views.py index 7cdf1b5474..4bc126544c 100644 --- a/fec/data/views.py +++ b/fec/data/views.py @@ -84,17 +84,7 @@ def advanced(request): }) -def candidate(request, candidate_id): - # grab url query string parameters - cycle = request.GET.get('cycle', None) - - election_full = request.GET.get('election_full', True) - - if cycle is not None: - cycle = int(cycle) - - # set election_full to boolean from passed string variable - election_full = bool(strtobool(str(election_full))) +def get_candidate(candidate_id, cycle, election_full): candidate, committees, cycle = api_caller.load_with_nested( 'candidate', candidate_id, 'committees', @@ -227,7 +217,7 @@ def candidate(request, candidate_id): reverse=True, ) - return render(request, 'candidates-single.jinja', { + return { 'name': candidate['name'], 'cycle': int(cycle), 'office': candidate['office'], @@ -258,7 +248,19 @@ def candidate(request, candidate_id): 'elections': elections, 'candidate': candidate, 'context_vars': context_vars - }) + } + + +def candidate(request, candidate_id): + cycle = request.GET.get('cycle', None) + if cycle is not None: + cycle = int(cycle) + + election_full = request.GET.get('election_full', True) + election_full = bool(strtobool(str(election_full))) + + candidate = get_candidate(candidate_id, cycle, election_full) + return render(request, 'candidates-single.jinja', candidate) def committee(request, committee_id): From 5369cb4f3e5c5de8bedc7b6053fcd3e395e83fd2 Mon Sep 17 00:00:00 2001 From: john carroll <jcarroll@fec.gov> Date: Sun, 26 Aug 2018 08:00:05 -0400 Subject: [PATCH 30/49] changed link to /campaign-finance-data/candidate-summary-file-description/ --- fec/data/templates/partials/advanced/candidates.jinja | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fec/data/templates/partials/advanced/candidates.jinja b/fec/data/templates/partials/advanced/candidates.jinja index 0ad91b5447..4868cfe996 100644 --- a/fec/data/templates/partials/advanced/candidates.jinja +++ b/fec/data/templates/partials/advanced/candidates.jinja @@ -23,7 +23,7 @@ <li><a href="/files/bulk-downloads/2010/candidate_summary_2010.csv">2009–2010</a></li> <li><a href="/files/bulk-downloads/2008/candidate_summary_2008.csv">2007-2008</a></li> </ul> - <p class="u-margin--top"><a href="/campaign-finance-data/all-candidates-file-description/">Data description for this file</a><i class="icon icon--small i-arrow-right icon--inline--right"></i></p> + <p class="u-margin--top"><a href="/campaign-finance-data/candidate-summary-file-description/">Data description for this file</a><i class="icon icon--small i-arrow-right icon--inline--right"></i></p> </div> <p>This file contains information for each candidate who has registered with the FEC or appears on an official state ballot for an election to the U.S. House of Representatives, U.S. Senate or U.S. President.</p> </div> From ab2d21a412530fd47fc1e16a340e649b0b957103 Mon Sep 17 00:00:00 2001 From: Laura Beaufort <31420082+lbeaufort@users.noreply.github.com> Date: Mon, 27 Aug 2018 17:23:05 -0400 Subject: [PATCH 31/49] Add test_candidate --- fec/data/tests/test_candidate.py | 471 +++++++++++++++++++++++++++++++ 1 file changed, 471 insertions(+) create mode 100644 fec/data/tests/test_candidate.py diff --git a/fec/data/tests/test_candidate.py b/fec/data/tests/test_candidate.py new file mode 100644 index 0000000000..a89d95ca5a --- /dev/null +++ b/fec/data/tests/test_candidate.py @@ -0,0 +1,471 @@ +from unittest import TestCase +from unittest import mock +import copy + +from data import api_caller +from data.views import get_candidate, groupby + + +@mock.patch.object(api_caller, 'load_candidate_statement_of_candidacy') +@mock.patch.object(api_caller, 'load_candidate_totals') +@mock.patch.object(api_caller, 'load_with_nested') +class TestCandidate(TestCase): + + STOCK_CANDIDATE = { + "candidate_id": "C001", + "name": "Lady Liberty", + "cycles": [2014, 2016, 2018], + "election_years": [2016, 2018], + "election_districts": ["01", "02"], + "office": "H", + "office_full": "House", + "state": "VA", + "district": "01", + "party_full": "Independent", + "incumbent_challenge_full": "Incumbent", + } + STOCK_COMMITTEE_LIST = [ + { + 'designation': 'P', + 'cycles': [2016, 2014, 2012, 2018], + 'name': 'My Primary Campaign Committee', + 'cycle': 2016, + 'committee_id': 'C001', + }, { + 'designation': 'A', + 'cycles': [2016, 2014, 2012, 2018], + 'name': 'My Authorized Campaign Committee', + 'cycle': 2016, + 'committee_id': 'C002', + }, { + 'designation': 'J', + 'cycles': [2016, 2014, 2012, 2018], + 'name': 'Joint Fundraising Committee', + 'cycle': 2016, + 'committee_id': 'C003', + } + ] + + def test_base_case(self, load_with_nested_mock, load_candidate_totals_mock, + load_candidate_statement_of_candidacy_mock): + cycle = 2016 + show_full_election = True + + load_with_nested_mock.return_value = ( + self.STOCK_CANDIDATE, + self.STOCK_COMMITTEE_LIST, + cycle + ) + candidate = get_candidate('C001', 2018, show_full_election) + + for field in self.STOCK_CANDIDATE: + if field != "election_districts": + assert candidate[field] == self.STOCK_CANDIDATE[field] + + assert candidate['cycle'] == cycle + assert candidate['election_year'] == cycle + assert candidate['result_type'] == 'candidates' + assert candidate['duration'] == 2 + assert candidate['min_cycle'] == cycle - 2 + assert candidate['report_type'] == 'house-senate' + assert candidate['max_cycle'] == cycle + assert candidate['show_full_election'] == show_full_election + + committee_groups = groupby( + self.STOCK_COMMITTEE_LIST, lambda each: each['designation'] + ) + + assert candidate['committee_groups'] == committee_groups + assert candidate['committees_authorized'] == committee_groups.get( + 'P', []) + committee_groups.get('A', []) + assert candidate['committee_ids'] == [ + committee['committee_id'] + for committee in candidate['committees_authorized'] + ] + + assert candidate['raising_summary'] == [] + assert candidate['spending_summary'] == [] + assert candidate['cash_summary'] == [] + assert candidate['elections'] == [(2018, '02'), (2016, '01')] + assert candidate['candidate'] == self.STOCK_CANDIDATE + assert candidate['context_vars'] == { + 'cycles': [2014, 2016, 2018], + 'name': self.STOCK_CANDIDATE["name"], + 'cycle': cycle, + 'electionFull': show_full_election, + 'candidateID': self.STOCK_CANDIDATE["candidate_id"] + } + + def test_special_election_election_years_only_rounded( + self, load_with_nested_mock, load_candidate_totals_mock, + load_candidate_statement_of_candidacy_mock): + + # Candidate ran in 2017 and 2018. Passing 2016 as cycle. + # Election years should be rounded, election year should be 2017 + candidate = copy.deepcopy(self.STOCK_CANDIDATE) + candidate["cycles"] = [2016, 2018] + candidate["election_years"] = [2017, 2018, 2019] + + load_with_nested_mock.return_value = ( + candidate, + mock.MagicMock(), + 2016 + ) + candidate = get_candidate('C001', 2016, True) + assert candidate["election_years"] == [2018, 2020] + assert candidate["election_year"] == 2017 + + def test_candidate_with_future_cycle_falls_back_to_present( + self, load_with_nested_mock, load_candidate_totals_mock, + load_candidate_statement_of_candidacy_mock): + + candidate = copy.deepcopy(self.STOCK_CANDIDATE) + candidate["election_years"] = [2018, 2100] + candidate["cycles"] = [2018] + candidate["office"] == 'S' + load_with_nested_mock.return_value = ( + candidate, + self.STOCK_COMMITTEE_LIST, + 2018 + ) + candidate = get_candidate('C001', 2100, True) + assert candidate["cycle"] == 2018 + assert candidate["cycles"] == [2018] + + def test_election_full_returns_false_nested_returns_future_cycle( + self, load_with_nested_mock, load_candidate_totals_mock, + load_candidate_statement_of_candidacy_mock): + + candidate = copy.deepcopy(self.STOCK_CANDIDATE) + committee = copy.deepcopy(self.STOCK_COMMITTEE_LIST[0]) + candidate["election_years"] = [2018, 2100] + committee["cycles"] = [2018, 2100] + + load_with_nested_mock.return_value = ( + candidate, + [committee], + 2100 + ) + candidate = get_candidate('C001', 2100, True) + assert candidate["show_full_election"] is False + + def test_aggregates(self, load_with_nested_mock, load_candidate_totals_mock, + load_candidate_statement_of_candidacy_mock): + load_with_nested_mock.return_value = ( + self.STOCK_CANDIDATE, + self.STOCK_COMMITTEE_LIST, + 2018 + ) + load_candidate_totals_mock.side_effect = [ + # 'aggregate' + { + 'loan_repayments_other_loans':0.0, + 'contributions': 26127221.0, + 'loan_repayments_candidate_loans': 0.0, + 'net_operating_expenditures': 13403095.0, + 'operating_expenditures': 13456880.0, + 'individual_contributions': 25695351.0, + 'full_election': True, + 'individual_itemized_contributions': 9017667.0, + 'political_party_committee_contributions': 2090.0, + 'cycle': 2018, + 'last_report_type_full': 'JULY QUARTERLY', + 'refunded_other_political_committee_contributions': 2493.0, + 'offsets_to_operating_expenditures': 53775.0, + 'last_debts_owed_by_committee': 0.0, + 'transaction_coverage_date': '2018-06-30T00:00:00+00:00', + 'loans_made_by_candidate': 0.0, + 'last_debts_owed_to_committee': 0.0, + 'disbursements': 14702788.0, + 'other_political_committee_contributions': 429778.0, + 'net_contributions': 25925858.0, + 'candidate_contribution': 0.0, + 'loan_repayments': 0.0, + 'last_net_contributions': 2279216.0, + 'total_offsets_to_operating_expenditures': None, + 'offsets_to_legal_accounting': None, + 'receipts': 30035777.0, + 'candidate_id': 'S2MA00170', + 'coverage_start_date': '2013-01-01T00:00:00+00:00', + 'last_cash_on_hand_end_period': 15627661.0, + 'all_other_loans': 0.0, + 'individual_unitemized_contributions': 16677677.0, + 'last_beginning_image_number': '201807190200580760', + 'other_disbursements': 1044533.0, + 'refunded_political_party_committee_contributions': 400.0, + 'other_receipts': 175793.0, + 'loans': 0.0, + 'coverage_end_date': '2018-06-30T00:00:00+00:00', + 'exempt_legal_accounting_disbursement': None, + 'federal_funds': None, + 'transfers_to_other_authorized_committee': 0.0, + 'refunded_individual_contributions': 198466.0, + 'contribution_refunds': 201361.0, + 'last_report_year': 2018, + 'offsets_to_fundraising_expenditures': None, + 'fundraising_disbursements': None, + 'last_net_operating_expenditures': 2253555.0, + 'transfers_from_other_authorized_committee': 3678961.0 + }, + # 'two_year_totals' + { + 'loan_repayments_other_loans': 0.0, + 'contributions': 16969092.0, + 'loan_repayments_candidate_loans': 0.0, + 'net_operating_expenditures': 9579415.0, + 'operating_expenditures': 9582171.0, + 'individual_contributions': 16636062.0, + 'full_election': False, + 'individual_itemized_contributions': 6176593.0, + 'political_party_committee_contributions': 0.0, + 'cycle': 2018, + 'last_report_type_full': 'JULY QUARTERLY', + 'refunded_other_political_committee_contributions': 1993.0, + 'offsets_to_operating_expenditures': 2752.0, + 'last_debts_owed_by_committee': 0.0, + 'transaction_coverage_date': '2018-06-30T00:00:00+00:00', + 'loans_made_by_candidate': 0.0, + 'last_debts_owed_to_committee': 0.0, + 'disbursements': 9873958.0, + 'other_political_committee_contributions': 333028.0, + 'net_contributions': 16834766.0, + 'candidate_contribution': 0.0, + 'loan_repayments': 0.0, + 'last_net_contributions': 2279216.0, + 'total_offsets_to_operating_expenditures': None, + 'offsets_to_legal_accounting': None, + 'receipts': 20656072.0, + 'candidate_id': 'S2MA00170', + 'coverage_start_date': '2017-01-01T00:00:00+00:00', + 'last_cash_on_hand_end_period': 15627661.0, + 'all_other_loans': 0.0, + 'individual_unitemized_contributions': 10459468.0, + 'last_beginning_image_number': '201807190200580760', + 'other_disbursements': 157458.0, + 'refunded_political_party_committee_contributions': 0.0, + 'other_receipts': 26563.0, + 'loans': 0.0, + 'coverage_end_date': '2018-06-30T00:00:00+00:00', + 'exempt_legal_accounting_disbursement': None, + 'federal_funds': None, + 'transfers_to_other_authorized_committee': 0.0, + 'refunded_individual_contributions': 132330.0, + 'contribution_refunds': 134324.0, + 'last_report_year': 2018, + 'offsets_to_fundraising_expenditures': None, + 'fundraising_disbursements': None, + 'last_net_operating_expenditures': 2253555.0, + 'transfers_from_other_authorized_committee': 3657657.0 + } + ] + candidate = get_candidate('S001', 2018, True) + + assert candidate["cash_summary"] == [(15627661.0, { + 'label': 'Ending cash on hand', + 'term': 'ending cash on hand', + 'level': '2' + }), (0.0, { + 'label': 'Debts/loans owed to committee', + 'level': '2' + }), (0.0, { + 'label': 'Debts/loans owed by committee', + 'level': '2' + })] + assert candidate["raising_summary"] == [(30035777.0, { + 'label': 'Total receipts', + 'level': '1', + 'term': 'total receipts' + }), (26127221.0, { + 'label': 'Total contributions', + 'level': '2' + }), (25695351.0, { + 'label': 'Total individual contributions', + 'level': '3' + }), (9017667.0, { + 'label': 'Itemized individual contributions', + 'level': '4', + 'type': { + 'link': 'receipts', + 'P': 'F3P-17A', + 'H': 'F3-11AI', + 'S': 'F3-11AI', + 'O': 'F3X-11AI' + } + }), (16677677.0, { + 'label': 'Unitemized individual contributions', + 'level': '4' + }), (2090.0, { + 'label': 'Party committee contributions', + 'level': '3', + 'type': { + 'link': 'receipts', + 'P': 'F3P-17B', + 'H': 'F3-11B', + 'S': 'F3-11B', + 'O': 'F3X-11B' + } + }), (429778.0, { + 'label': 'Other committee contributions', + 'level': '3', + 'type': { + 'link': 'receipts', + 'P': 'F3P-17C', + 'H': 'F3-11C', + 'S': 'F3-11C', + 'O': 'F3X-11C' + } + }), (0.0, { + 'label': 'Candidate contributions', + 'level': '3', + 'type': { + 'link': 'receipts', + 'P': 'F3P-17D', + 'H': 'F3-11D', + 'S': 'F3-11D' + } + }), (3678961.0, { + 'label': 'Transfers from other authorized committees', + 'level': '2', + 'type': { + 'link': 'receipts', + 'H': 'F3-12', + 'S': 'F3-12' + } + }), (0.0, { + 'label': 'Total loans received', + 'level': '2' + }), (0.0, { + 'label': 'Loans made by candidate', + 'level': '3', + 'type': { + 'link': 'receipts', + 'H': 'F3-13A', + 'S': 'F3-13A' + } + }), (0.0, { + 'label': 'Other loans', + 'level': '3', + 'type': { + 'link': 'receipts', + 'H': 'F3-13B', + 'S': 'F3-13B' + } + }), (53775.0, { + 'label': 'Offsets to operating expenditures', + 'level': '2', + 'type': { + 'link': 'receipts', + 'H': 'F3-14', + 'S': 'F3-14', + 'O': 'F3X-15' + } + }), (175793.0, { + 'label': 'Other receipts', + 'level': '2', + 'type': { + 'link': 'receipts', + 'P': 'F3P-21', + 'H': 'F3-15', + 'S': 'F3-15' + } + })] + + assert candidate["spending_summary"] == [(14702788.0, { + 'label': 'Total disbursements', + 'level': '1', + 'term': 'total disbursements' + }), (13456880.0, { + 'label': 'Operating expenditures', + 'term': 'operating expenditures', + 'level': '2', + 'type': { + 'link': 'disbursements', + 'P': 'F3P-23', + 'H': 'F3-17', + 'S': 'F3-17' + } + }), (0.0, { + 'label': 'Transfers to other authorized committees', + 'level': '2', + 'type': { + 'link': 'disbursements', + 'H': 'F3-18', + 'S': 'F3-18', + 'P': 'F3P-24' + } + }), (201361.0, { + 'label': 'Total contribution refunds', + 'level': '2' + }), (198466.0, { + 'label': 'Individual refunds', + 'level': '3', + 'type': { + 'link': 'disbursements', + 'P': 'F3P-28A', + 'H': 'F3-20A', + 'S': 'F3-20A', + 'O': 'F3X-28A' + } + }), (400.0, { + 'label': 'Political party refunds', + 'level': '3', + 'type': { + 'link': 'disbursements', + 'P': 'F3P-28B', + 'H': 'F3-20B', + 'S': 'F3-20B', + 'O': 'F3X-28B' + } + }), (2493.0, { + 'label': 'Other committee refunds', + 'level': '3', + 'type': { + 'link': 'disbursements', + 'P': 'F3P-28C', + 'H': 'F3-20C', + 'S': 'F3-20C', + 'O': 'F3X-28C' + } + }), (0.0, { + 'label': 'Total loan repayments', + 'level': '2' + }), (0.0, { + 'label': 'Candidate loan repayments', + 'level': '3', + 'type': { + 'link': 'disbursements', + 'H': 'F3-19A', + 'S': 'F3-19A' + } + }), (0.0, { + 'label': 'Other loan repayments', + 'level': '3', + 'type': { + 'link': 'disbursements', + 'H': 'F3-19B', + 'S': 'F3-19B' + } + }), (1044533.0, { + 'label': 'Other disbursements', + 'level': '2', + 'type': { + 'link': 'disbursements', + 'P': 'F3P-29', + 'H': 'F3-21', + 'S': 'F3-21', + 'O': 'F3X-29' + } + })] + + def test_no_aggregates(self, load_with_nested_mock, load_candidate_totals_mock, + load_candidate_statement_of_candidacy_mock): + load_with_nested_mock.return_value = ( + self.STOCK_CANDIDATE, + self.STOCK_COMMITTEE_LIST, + 2018 + ) + load_candidate_totals_mock.side_effect = [{}, {}] + candidate = get_candidate('S001', 2018, True) + assert candidate["cash_summary"] is None + assert candidate["raising_summary"] is None + assert candidate["spending_summary"] is None From 81c768dcc73d1599e52770923d2e1ee3efa92428 Mon Sep 17 00:00:00 2001 From: Vraj Mohan <radhakrishnan.vrajmohan@gsa.gov> Date: Mon, 27 Aug 2018 19:15:54 -0700 Subject: [PATCH 32/49] Refactor test code * Format code to meet PEP8 * Use explicit property assertions in base case * Use separate variable for test data * Use explicit assertions for computed data --- fec/data/tests/test_candidate.py | 435 +++++-------------------------- 1 file changed, 72 insertions(+), 363 deletions(-) diff --git a/fec/data/tests/test_candidate.py b/fec/data/tests/test_candidate.py index a89d95ca5a..5a17516994 100644 --- a/fec/data/tests/test_candidate.py +++ b/fec/data/tests/test_candidate.py @@ -3,7 +3,7 @@ import copy from data import api_caller -from data.views import get_candidate, groupby +from data.views import get_candidate @mock.patch.object(api_caller, 'load_candidate_statement_of_candidacy') @@ -51,63 +51,93 @@ def test_base_case(self, load_with_nested_mock, load_candidate_totals_mock, cycle = 2016 show_full_election = True + test_candidate = copy.deepcopy(self.STOCK_CANDIDATE) load_with_nested_mock.return_value = ( - self.STOCK_CANDIDATE, + test_candidate, self.STOCK_COMMITTEE_LIST, cycle ) + load_candidate_totals_mock.return_value = {} candidate = get_candidate('C001', 2018, show_full_election) - for field in self.STOCK_CANDIDATE: - if field != "election_districts": - assert candidate[field] == self.STOCK_CANDIDATE[field] - - assert candidate['cycle'] == cycle + assert candidate['candidate_id'] == test_candidate['candidate_id'] + assert candidate['name'] == test_candidate['name'] + assert candidate['cycles'] == test_candidate['cycles'] + assert candidate['min_cycle'] == cycle - 2 + assert candidate['max_cycle'] == cycle assert candidate['election_year'] == cycle + assert candidate['election_years'] == test_candidate['election_years'] + assert candidate['office'] == test_candidate['office'] + assert candidate['office_full'] == test_candidate['office_full'] + assert candidate['state'] == test_candidate['state'] + assert candidate['district'] == test_candidate['district'] + assert candidate['party_full'] == test_candidate['party_full'] + assert candidate['incumbent_challenge_full'] == test_candidate[ + 'incumbent_challenge_full'] + assert candidate['cycle'] == cycle assert candidate['result_type'] == 'candidates' assert candidate['duration'] == 2 - assert candidate['min_cycle'] == cycle - 2 assert candidate['report_type'] == 'house-senate' - assert candidate['max_cycle'] == cycle assert candidate['show_full_election'] == show_full_election - committee_groups = groupby( - self.STOCK_COMMITTEE_LIST, lambda each: each['designation'] - ) + assert candidate['committee_groups'] == { + 'P': [{ + 'designation': 'P', + 'cycles': [2016, 2014, 2012, 2018], + 'name': 'My Primary Campaign Committee', + 'cycle': 2016, + 'related_cycle': 2016, + 'committee_id': 'C001'}], + 'A': [{ + 'designation': 'A', + 'cycles': [2016, 2014, 2012, 2018], + 'name': 'My Authorized Campaign Committee', + 'cycle': 2016, + 'related_cycle': 2016, + 'committee_id': 'C002'}], + 'J': [{ + 'designation': 'J', + 'cycles': [2016, 2014, 2012, 2018], + 'name': 'Joint Fundraising Committee', + 'cycle': 2016, + 'related_cycle': 2016, + 'committee_id': 'C003'}] + } - assert candidate['committee_groups'] == committee_groups - assert candidate['committees_authorized'] == committee_groups.get( - 'P', []) + committee_groups.get('A', []) + assert candidate['committees_authorized'] == ( + candidate['committee_groups']['P'] + + candidate['committee_groups']['A']) assert candidate['committee_ids'] == [ committee['committee_id'] for committee in candidate['committees_authorized'] ] - assert candidate['raising_summary'] == [] - assert candidate['spending_summary'] == [] - assert candidate['cash_summary'] == [] assert candidate['elections'] == [(2018, '02'), (2016, '01')] - assert candidate['candidate'] == self.STOCK_CANDIDATE + assert candidate['candidate'] == test_candidate assert candidate['context_vars'] == { 'cycles': [2014, 2016, 2018], - 'name': self.STOCK_CANDIDATE["name"], + 'name': test_candidate["name"], 'cycle': cycle, 'electionFull': show_full_election, - 'candidateID': self.STOCK_CANDIDATE["candidate_id"] + 'candidateID': test_candidate["candidate_id"] } + assert candidate['raising_summary'] is None + assert candidate['spending_summary'] is None + assert candidate['cash_summary'] is None + def test_special_election_election_years_only_rounded( - self, load_with_nested_mock, load_candidate_totals_mock, - load_candidate_statement_of_candidacy_mock): + self, load_with_nested_mock, load_candidate_totals_mock, + load_candidate_statement_of_candidacy_mock): # Candidate ran in 2017 and 2018. Passing 2016 as cycle. # Election years should be rounded, election year should be 2017 - candidate = copy.deepcopy(self.STOCK_CANDIDATE) - candidate["cycles"] = [2016, 2018] - candidate["election_years"] = [2017, 2018, 2019] + test_candidate = copy.deepcopy(self.STOCK_CANDIDATE) + test_candidate["cycles"] = [2016, 2018] + test_candidate["election_years"] = [2017, 2018, 2019] load_with_nested_mock.return_value = ( - candidate, + test_candidate, mock.MagicMock(), 2016 ) @@ -116,15 +146,15 @@ def test_special_election_election_years_only_rounded( assert candidate["election_year"] == 2017 def test_candidate_with_future_cycle_falls_back_to_present( - self, load_with_nested_mock, load_candidate_totals_mock, - load_candidate_statement_of_candidacy_mock): + self, load_with_nested_mock, load_candidate_totals_mock, + load_candidate_statement_of_candidacy_mock): - candidate = copy.deepcopy(self.STOCK_CANDIDATE) - candidate["election_years"] = [2018, 2100] - candidate["cycles"] = [2018] - candidate["office"] == 'S' + test_candidate = copy.deepcopy(self.STOCK_CANDIDATE) + test_candidate["election_years"] = [2018, 2100] + test_candidate["cycles"] = [2018] + test_candidate["office"] == 'S' load_with_nested_mock.return_value = ( - candidate, + test_candidate, self.STOCK_COMMITTEE_LIST, 2018 ) @@ -133,339 +163,18 @@ def test_candidate_with_future_cycle_falls_back_to_present( assert candidate["cycles"] == [2018] def test_election_full_returns_false_nested_returns_future_cycle( - self, load_with_nested_mock, load_candidate_totals_mock, - load_candidate_statement_of_candidacy_mock): + self, load_with_nested_mock, load_candidate_totals_mock, + load_candidate_statement_of_candidacy_mock): - candidate = copy.deepcopy(self.STOCK_CANDIDATE) - committee = copy.deepcopy(self.STOCK_COMMITTEE_LIST[0]) - candidate["election_years"] = [2018, 2100] - committee["cycles"] = [2018, 2100] + test_candidate = copy.deepcopy(self.STOCK_CANDIDATE) + test_committee = copy.deepcopy(self.STOCK_COMMITTEE_LIST[0]) + test_candidate["election_years"] = [2018, 2100] + test_committee["cycles"] = [2018, 2100] load_with_nested_mock.return_value = ( - candidate, - [committee], + test_candidate, + [test_committee], 2100 ) candidate = get_candidate('C001', 2100, True) assert candidate["show_full_election"] is False - - def test_aggregates(self, load_with_nested_mock, load_candidate_totals_mock, - load_candidate_statement_of_candidacy_mock): - load_with_nested_mock.return_value = ( - self.STOCK_CANDIDATE, - self.STOCK_COMMITTEE_LIST, - 2018 - ) - load_candidate_totals_mock.side_effect = [ - # 'aggregate' - { - 'loan_repayments_other_loans':0.0, - 'contributions': 26127221.0, - 'loan_repayments_candidate_loans': 0.0, - 'net_operating_expenditures': 13403095.0, - 'operating_expenditures': 13456880.0, - 'individual_contributions': 25695351.0, - 'full_election': True, - 'individual_itemized_contributions': 9017667.0, - 'political_party_committee_contributions': 2090.0, - 'cycle': 2018, - 'last_report_type_full': 'JULY QUARTERLY', - 'refunded_other_political_committee_contributions': 2493.0, - 'offsets_to_operating_expenditures': 53775.0, - 'last_debts_owed_by_committee': 0.0, - 'transaction_coverage_date': '2018-06-30T00:00:00+00:00', - 'loans_made_by_candidate': 0.0, - 'last_debts_owed_to_committee': 0.0, - 'disbursements': 14702788.0, - 'other_political_committee_contributions': 429778.0, - 'net_contributions': 25925858.0, - 'candidate_contribution': 0.0, - 'loan_repayments': 0.0, - 'last_net_contributions': 2279216.0, - 'total_offsets_to_operating_expenditures': None, - 'offsets_to_legal_accounting': None, - 'receipts': 30035777.0, - 'candidate_id': 'S2MA00170', - 'coverage_start_date': '2013-01-01T00:00:00+00:00', - 'last_cash_on_hand_end_period': 15627661.0, - 'all_other_loans': 0.0, - 'individual_unitemized_contributions': 16677677.0, - 'last_beginning_image_number': '201807190200580760', - 'other_disbursements': 1044533.0, - 'refunded_political_party_committee_contributions': 400.0, - 'other_receipts': 175793.0, - 'loans': 0.0, - 'coverage_end_date': '2018-06-30T00:00:00+00:00', - 'exempt_legal_accounting_disbursement': None, - 'federal_funds': None, - 'transfers_to_other_authorized_committee': 0.0, - 'refunded_individual_contributions': 198466.0, - 'contribution_refunds': 201361.0, - 'last_report_year': 2018, - 'offsets_to_fundraising_expenditures': None, - 'fundraising_disbursements': None, - 'last_net_operating_expenditures': 2253555.0, - 'transfers_from_other_authorized_committee': 3678961.0 - }, - # 'two_year_totals' - { - 'loan_repayments_other_loans': 0.0, - 'contributions': 16969092.0, - 'loan_repayments_candidate_loans': 0.0, - 'net_operating_expenditures': 9579415.0, - 'operating_expenditures': 9582171.0, - 'individual_contributions': 16636062.0, - 'full_election': False, - 'individual_itemized_contributions': 6176593.0, - 'political_party_committee_contributions': 0.0, - 'cycle': 2018, - 'last_report_type_full': 'JULY QUARTERLY', - 'refunded_other_political_committee_contributions': 1993.0, - 'offsets_to_operating_expenditures': 2752.0, - 'last_debts_owed_by_committee': 0.0, - 'transaction_coverage_date': '2018-06-30T00:00:00+00:00', - 'loans_made_by_candidate': 0.0, - 'last_debts_owed_to_committee': 0.0, - 'disbursements': 9873958.0, - 'other_political_committee_contributions': 333028.0, - 'net_contributions': 16834766.0, - 'candidate_contribution': 0.0, - 'loan_repayments': 0.0, - 'last_net_contributions': 2279216.0, - 'total_offsets_to_operating_expenditures': None, - 'offsets_to_legal_accounting': None, - 'receipts': 20656072.0, - 'candidate_id': 'S2MA00170', - 'coverage_start_date': '2017-01-01T00:00:00+00:00', - 'last_cash_on_hand_end_period': 15627661.0, - 'all_other_loans': 0.0, - 'individual_unitemized_contributions': 10459468.0, - 'last_beginning_image_number': '201807190200580760', - 'other_disbursements': 157458.0, - 'refunded_political_party_committee_contributions': 0.0, - 'other_receipts': 26563.0, - 'loans': 0.0, - 'coverage_end_date': '2018-06-30T00:00:00+00:00', - 'exempt_legal_accounting_disbursement': None, - 'federal_funds': None, - 'transfers_to_other_authorized_committee': 0.0, - 'refunded_individual_contributions': 132330.0, - 'contribution_refunds': 134324.0, - 'last_report_year': 2018, - 'offsets_to_fundraising_expenditures': None, - 'fundraising_disbursements': None, - 'last_net_operating_expenditures': 2253555.0, - 'transfers_from_other_authorized_committee': 3657657.0 - } - ] - candidate = get_candidate('S001', 2018, True) - - assert candidate["cash_summary"] == [(15627661.0, { - 'label': 'Ending cash on hand', - 'term': 'ending cash on hand', - 'level': '2' - }), (0.0, { - 'label': 'Debts/loans owed to committee', - 'level': '2' - }), (0.0, { - 'label': 'Debts/loans owed by committee', - 'level': '2' - })] - assert candidate["raising_summary"] == [(30035777.0, { - 'label': 'Total receipts', - 'level': '1', - 'term': 'total receipts' - }), (26127221.0, { - 'label': 'Total contributions', - 'level': '2' - }), (25695351.0, { - 'label': 'Total individual contributions', - 'level': '3' - }), (9017667.0, { - 'label': 'Itemized individual contributions', - 'level': '4', - 'type': { - 'link': 'receipts', - 'P': 'F3P-17A', - 'H': 'F3-11AI', - 'S': 'F3-11AI', - 'O': 'F3X-11AI' - } - }), (16677677.0, { - 'label': 'Unitemized individual contributions', - 'level': '4' - }), (2090.0, { - 'label': 'Party committee contributions', - 'level': '3', - 'type': { - 'link': 'receipts', - 'P': 'F3P-17B', - 'H': 'F3-11B', - 'S': 'F3-11B', - 'O': 'F3X-11B' - } - }), (429778.0, { - 'label': 'Other committee contributions', - 'level': '3', - 'type': { - 'link': 'receipts', - 'P': 'F3P-17C', - 'H': 'F3-11C', - 'S': 'F3-11C', - 'O': 'F3X-11C' - } - }), (0.0, { - 'label': 'Candidate contributions', - 'level': '3', - 'type': { - 'link': 'receipts', - 'P': 'F3P-17D', - 'H': 'F3-11D', - 'S': 'F3-11D' - } - }), (3678961.0, { - 'label': 'Transfers from other authorized committees', - 'level': '2', - 'type': { - 'link': 'receipts', - 'H': 'F3-12', - 'S': 'F3-12' - } - }), (0.0, { - 'label': 'Total loans received', - 'level': '2' - }), (0.0, { - 'label': 'Loans made by candidate', - 'level': '3', - 'type': { - 'link': 'receipts', - 'H': 'F3-13A', - 'S': 'F3-13A' - } - }), (0.0, { - 'label': 'Other loans', - 'level': '3', - 'type': { - 'link': 'receipts', - 'H': 'F3-13B', - 'S': 'F3-13B' - } - }), (53775.0, { - 'label': 'Offsets to operating expenditures', - 'level': '2', - 'type': { - 'link': 'receipts', - 'H': 'F3-14', - 'S': 'F3-14', - 'O': 'F3X-15' - } - }), (175793.0, { - 'label': 'Other receipts', - 'level': '2', - 'type': { - 'link': 'receipts', - 'P': 'F3P-21', - 'H': 'F3-15', - 'S': 'F3-15' - } - })] - - assert candidate["spending_summary"] == [(14702788.0, { - 'label': 'Total disbursements', - 'level': '1', - 'term': 'total disbursements' - }), (13456880.0, { - 'label': 'Operating expenditures', - 'term': 'operating expenditures', - 'level': '2', - 'type': { - 'link': 'disbursements', - 'P': 'F3P-23', - 'H': 'F3-17', - 'S': 'F3-17' - } - }), (0.0, { - 'label': 'Transfers to other authorized committees', - 'level': '2', - 'type': { - 'link': 'disbursements', - 'H': 'F3-18', - 'S': 'F3-18', - 'P': 'F3P-24' - } - }), (201361.0, { - 'label': 'Total contribution refunds', - 'level': '2' - }), (198466.0, { - 'label': 'Individual refunds', - 'level': '3', - 'type': { - 'link': 'disbursements', - 'P': 'F3P-28A', - 'H': 'F3-20A', - 'S': 'F3-20A', - 'O': 'F3X-28A' - } - }), (400.0, { - 'label': 'Political party refunds', - 'level': '3', - 'type': { - 'link': 'disbursements', - 'P': 'F3P-28B', - 'H': 'F3-20B', - 'S': 'F3-20B', - 'O': 'F3X-28B' - } - }), (2493.0, { - 'label': 'Other committee refunds', - 'level': '3', - 'type': { - 'link': 'disbursements', - 'P': 'F3P-28C', - 'H': 'F3-20C', - 'S': 'F3-20C', - 'O': 'F3X-28C' - } - }), (0.0, { - 'label': 'Total loan repayments', - 'level': '2' - }), (0.0, { - 'label': 'Candidate loan repayments', - 'level': '3', - 'type': { - 'link': 'disbursements', - 'H': 'F3-19A', - 'S': 'F3-19A' - } - }), (0.0, { - 'label': 'Other loan repayments', - 'level': '3', - 'type': { - 'link': 'disbursements', - 'H': 'F3-19B', - 'S': 'F3-19B' - } - }), (1044533.0, { - 'label': 'Other disbursements', - 'level': '2', - 'type': { - 'link': 'disbursements', - 'P': 'F3P-29', - 'H': 'F3-21', - 'S': 'F3-21', - 'O': 'F3X-29' - } - })] - - def test_no_aggregates(self, load_with_nested_mock, load_candidate_totals_mock, - load_candidate_statement_of_candidacy_mock): - load_with_nested_mock.return_value = ( - self.STOCK_CANDIDATE, - self.STOCK_COMMITTEE_LIST, - 2018 - ) - load_candidate_totals_mock.side_effect = [{}, {}] - candidate = get_candidate('S001', 2018, True) - assert candidate["cash_summary"] is None - assert candidate["raising_summary"] is None - assert candidate["spending_summary"] is None From 2aa082822ee360acc98ac1becfd3c6074dc65480 Mon Sep 17 00:00:00 2001 From: Laura Beaufort <31420082+lbeaufort@users.noreply.github.com> Date: Tue, 28 Aug 2018 14:55:21 -0400 Subject: [PATCH 33/49] Keep cycle consistent for base case --- fec/data/tests/test_candidate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fec/data/tests/test_candidate.py b/fec/data/tests/test_candidate.py index 5a17516994..20e94f14bb 100644 --- a/fec/data/tests/test_candidate.py +++ b/fec/data/tests/test_candidate.py @@ -58,7 +58,7 @@ def test_base_case(self, load_with_nested_mock, load_candidate_totals_mock, cycle ) load_candidate_totals_mock.return_value = {} - candidate = get_candidate('C001', 2018, show_full_election) + candidate = get_candidate('C001', cycle, show_full_election) assert candidate['candidate_id'] == test_candidate['candidate_id'] assert candidate['name'] == test_candidate['name'] From bf61f0cc1da61932dc3b8f5c81c3f95b63a06698 Mon Sep 17 00:00:00 2001 From: Vraj Mohan <radhakrishnan.vrajmohan@gsa.gov> Date: Wed, 22 Aug 2018 12:55:28 -0700 Subject: [PATCH 34/49] Reformat the code Fix line lengths, whitespace, remove statements that do nothing --- fec/data/views.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/fec/data/views.py b/fec/data/views.py index 4bc126544c..765366e89b 100644 --- a/fec/data/views.py +++ b/fec/data/views.py @@ -91,8 +91,10 @@ def get_candidate(candidate_id, cycle, election_full): cycle=cycle, cycle_key='two_year_period', election_full=election_full, ) - # cycle corresponds to the two-year period for which the committee has financial activity. - # when selected election cycle is not in list of election years, get the next election cycle + + # cycle corresponds to the two-year period for which the committee has + # financial activity. when selected election cycle is not in list of + # election years, get the next election cycle if election_full and cycle and cycle not in candidate['election_years']: next_cycle = next( @@ -103,8 +105,8 @@ def get_candidate(candidate_id, cycle, election_full): max(candidate['election_years']), ) - # If the next_cycle is odd set it to whatever the cycle value was- falls back to the cycle - # and then set election_full to false + # If the next_cycle is odd set it to whatever the cycle value was- + # falls back to the cycle and then set election_full to false # This solves issue# 1945 with odd year special elections if next_cycle % 2 > 0: next_cycle = cycle @@ -117,7 +119,8 @@ def get_candidate(candidate_id, cycle, election_full): ) election_year = next( - (year for year in sorted(candidate['election_years']) if year >= cycle), + (year for year in sorted(candidate['election_years']) + if year >= cycle), None, ) @@ -135,10 +138,11 @@ def get_candidate(candidate_id, cycle, election_full): 'candidateID': candidate['candidate_id'] } - # Addresses issue#1644 - make any odd year special election an even year + # Addresses issue#1644 - make any odd year special election an even year # for displaying elections for pulldown menu in Candidate pages # Using Set to ensure no duplicate years in final list - even_election_years = list({ year + (year % 2) for year in candidate.get('election_years', []) }) + even_election_years = list( + {year + (year % 2) for year in candidate.get('election_years', [])}) # In the case of when a presidential or senate candidate has filed # for a future year that's beyond the current cycle, @@ -159,7 +163,8 @@ def get_candidate(candidate_id, cycle, election_full): ) for committee in committees: committee['related_cycle'] = ( - max(cycle for cycle in aggregate_cycles if cycle in committee['cycles']) + max(cycle for cycle in aggregate_cycles + if cycle in committee['cycles']) if election_full else candidate['two_year_period'] ) @@ -168,8 +173,6 @@ def get_candidate(candidate_id, cycle, election_full): committee_groups = groupby(committees, lambda each: each['designation']) committees_authorized = committee_groups.get('P', []) + committee_groups.get('A', []) - committee_groups = committee_groups - committees_authorized = committees_authorized committee_ids = [committee['committee_id'] for committee in committees_authorized] # Get aggregate totals for the financial summary From 70be68e365f8bd2d44c4b3fb6e9361659532e2fc Mon Sep 17 00:00:00 2001 From: Laura Beaufort <31420082+lbeaufort@users.noreply.github.com> Date: Tue, 28 Aug 2018 10:50:41 -0400 Subject: [PATCH 35/49] Move statement of candidacy receipt_date formatting to jinja template Use @library.filter filters.date() --- fec/data/templates/partials/candidate/about-candidate.jinja | 2 +- fec/data/views.py | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/fec/data/templates/partials/candidate/about-candidate.jinja b/fec/data/templates/partials/candidate/about-candidate.jinja index 1edcb14742..cf5cae924f 100644 --- a/fec/data/templates/partials/candidate/about-candidate.jinja +++ b/fec/data/templates/partials/candidate/about-candidate.jinja @@ -48,7 +48,7 @@ {% if statement.fec_file_id %} <div class="t-small u-small-icon-padding--left"> {{ statement.fec_file_id }}</div> {% endif %} - <div class="u-small-icon-padding--left"> Filed {{ statement.receipt_date }}</div> + <div class="u-small-icon-padding--left"> Filed {{ statement.receipt_date|date }}</div> </li> {% endfor %} </ul> diff --git a/fec/data/views.py b/fec/data/views.py index 765366e89b..0029f68b94 100644 --- a/fec/data/views.py +++ b/fec/data/views.py @@ -207,12 +207,6 @@ def get_candidate(candidate_id, cycle, election_full): cycle=cycle ) - if statement_of_candidacy: - for statement in statement_of_candidacy: - # convert string to python datetime and parse for readable output - statement['receipt_date'] = datetime.datetime.strptime(statement['receipt_date'], '%Y-%m-%dT%H:%M:%S') - statement['receipt_date'] = statement['receipt_date'].strftime('%m/%d/%Y') - # Get all the elections elections = sorted( zip(candidate['election_years'], candidate['election_districts']), From c430f4a081a01db4b5ab7629131cd897938ab48a Mon Sep 17 00:00:00 2001 From: Laura Beaufort <31420082+lbeaufort@users.noreply.github.com> Date: Tue, 28 Aug 2018 11:08:27 -0400 Subject: [PATCH 36/49] Replace max_cycle with cycle max_cycle and cycle will always be the same value --- .../candidate/financial-summary.jinja | 4 ++-- .../candidate/other-spending-tab.jinja | 22 +++++++++---------- .../partials/candidate/raising.jinja | 18 ++++++--------- .../partials/candidate/spending.jinja | 12 ++++------ fec/data/tests/test_candidate.py | 6 ++--- fec/data/views.py | 5 ++--- 6 files changed, 29 insertions(+), 38 deletions(-) diff --git a/fec/data/templates/partials/candidate/financial-summary.jinja b/fec/data/templates/partials/candidate/financial-summary.jinja index c792847978..a1dd45526a 100644 --- a/fec/data/templates/partials/candidate/financial-summary.jinja +++ b/fec/data/templates/partials/candidate/financial-summary.jinja @@ -30,7 +30,7 @@ <div class="heading--section heading--with-action"> <h3 class="heading__left">Total raised</h3> <a class="heading__right button--alt button--browse" - href="/data/receipts/?{% for committee in committee_ids %}committee_id={{ committee}}&{% endfor %}two_year_transaction_period={{ max_cycle }}">Browse receipts + href="/data/receipts/?{% for committee in committee_ids %}committee_id={{ committee}}&{% endfor %}two_year_transaction_period={{ cycle }}">Browse receipts </a> </div> <div class="tag__category u-no-margin"> @@ -42,7 +42,7 @@ <div class="heading--section heading--with-action"> <h3 class="heading__left">Total spent</h3> <a class="heading__right button--alt button--browse" - href="/data/disbursements/?{% for committee in committee_ids %}committee_id={{ committee}}&{% endfor %}two_year_transaction_period={{ max_cycle }}">Browse disbursements + href="/data/disbursements/?{% for committee in committee_ids %}committee_id={{ committee}}&{% endfor %}two_year_transaction_period={{ cycle }}">Browse disbursements </a> </div> <div class="tag__category u-no-margin"> diff --git a/fec/data/templates/partials/candidate/other-spending-tab.jinja b/fec/data/templates/partials/candidate/other-spending-tab.jinja index 70bbe422dd..0e6f027f9a 100644 --- a/fec/data/templates/partials/candidate/other-spending-tab.jinja +++ b/fec/data/templates/partials/candidate/other-spending-tab.jinja @@ -15,25 +15,25 @@ <div class="heading--section heading--with-action"> <h3 class="heading__left">Independent expenditures</h3> <a class="heading__right button--alt button--browse" - href="/data/independent-expenditures/?min_date={{ cycle_start(min_cycle) | date(fmt='%m-%d-%Y') }}&max_date={{ cycle_end(max_cycle) | date(fmt='%m-%d-%Y') }}&candidate_id={{ candidate_id }}">Filter this data</a> + href="/data/independent-expenditures/?min_date={{ cycle_start(min_cycle) | date(fmt='%m-%d-%Y') }}&max_date={{ cycle_end(cycle) | date(fmt='%m-%d-%Y') }}&candidate_id={{ candidate_id }}">Filter this data</a> </div> <div class="results-info results-info--simple js-other-spending-totals" data-spending-type="independentExpenditures"> <div class="usa-width-one-half"> <span class="label">Support</span> <span class="t-big-data js-support"><img src="{{ static('img/loading-ellipsis.gif') }}" alt="Loading indicator"></span> - <span class="t-block t-sans">Spent by others to <strong>support</strong> from <strong>{{cycle_start(min_cycle)|date_full}} to {{cycle_end(max_cycle)|date_full}}.</strong></span> + <span class="t-block t-sans">Spent by others to <strong>support</strong> from <strong>{{cycle_start(min_cycle)|date_full}} to {{cycle_end(cycle)|date_full}}.</strong></span> </div> <div class="usa-width-one-half"> <span class="label">Oppose</span> <span class="t-big-data js-oppose"><img src="{{ static('img/loading-ellipsis.gif') }}" alt="Loading indicator"></span> - <span class="t-block t-sans">Spent by others to <strong>oppose</strong> from <strong>{{cycle_start(min_cycle)|date_full}} to {{cycle_end(max_cycle)|date_full}}.</strong></span> + <span class="t-block t-sans">Spent by others to <strong>oppose</strong> from <strong>{{cycle_start(min_cycle)|date_full}} to {{cycle_end(cycle)|date_full}}.</strong></span> </div> </div> <table class="data-table data-table--heading-borders" data-type="independent-expenditures" data-candidate="{{ candidate_id }}" - data-cycle="{{ max_cycle }}" + data-cycle="{{ cycle }}" data-election-full="{{ show_full_election }}" data-duration="{% if election_full %}{{ duration }}{% else %}2{% endif %}" > @@ -57,26 +57,26 @@ href="{{ url_for( 'communication_costs', min_date=cycle_start(min_cycle) | date(fmt='%m-%d-%Y'), - max_date=cycle_end(max_cycle) | date(fmt='%m-%d-%Y') + max_date=cycle_end(cycle) | date(fmt='%m-%d-%Y') ) }}&candidate_id={{ candidate_id }}">Filter this data</a> #} </div> <div class="results-info results-info--simple js-other-spending-totals" data-spending-type="communicationCosts"> <div class="usa-width-one-half"> <span class="label">Support</span> <span class="t-big-data js-support"><img src="{{ static('img/loading-ellipsis.gif') }}" alt="Loading indicator"></span> - <span class="t-block t-sans">Spent by others to <strong>support</strong> from <strong>{{cycle_start(min_cycle)|date_full}} to {{cycle_end(max_cycle)|date_full}}.</strong></span> + <span class="t-block t-sans">Spent by others to <strong>support</strong> from <strong>{{cycle_start(min_cycle)|date_full}} to {{cycle_end(cycle)|date_full}}.</strong></span> </div> <div class="usa-width-one-half"> <span class="label">Oppose</span> <span class="t-big-data js-oppose"><img src="{{ static('img/loading-ellipsis.gif') }}" alt="Loading indicator"></span> - <span class="t-block t-sans">Spent by others to <strong>support</strong> from <strong>{{cycle_start(min_cycle)|date_full}} to {{cycle_end(max_cycle)|date_full}}.</strong></span> + <span class="t-block t-sans">Spent by others to <strong>support</strong> from <strong>{{cycle_start(min_cycle)|date_full}} to {{cycle_end(cycle)|date_full}}.</strong></span> </div> </div> <table class="data-table data-table--heading-borders" data-type="communication-costs" data-candidate="{{ candidate_id }}" - data-cycle="{{ max_cycle }}" + data-cycle="{{ cycle }}" data-election-full="{{ show_full_election }}" data-duration="{% if election_full %}{{ duration }}{% else %}2{% endif %}" > @@ -94,12 +94,12 @@ <div class="heading--section heading--with-action u-no-margin"> <h3 class="heading__left">Electioneering communication</h3> <a class="heading__right button--alt button--browse" - href="/data/electioneering-communications/?min_date={{ cycle_start(min_cycle) | date(fmt='%m-%d-%Y') }}&max_date={{ cycle_end(max_cycle) | date(fmt='%m-%d-%Y') }}&candidate_id={{ candidate_id }}">Filter this data</a> + href="/data/electioneering-communications/?min_date={{ cycle_start(min_cycle) | date(fmt='%m-%d-%Y') }}&max_date={{ cycle_end(cycle) | date(fmt='%m-%d-%Y') }}&candidate_id={{ candidate_id }}">Filter this data</a> </div> <div class="results-info results-info--simple js-other-spending-totals" data-spending-type="electioneering"> <div class="usa-width-one-half"> <span class="t-big-data js-total-electioneering u-margin--top"><img src="{{ static('img/loading-ellipsis.gif') }}" alt="Loading indicator"></span> - <span class="t-block t-sans">Spent by others that mentioned this candidate <strong>{{cycle_start(min_cycle)|date_full}} to {{cycle_end(max_cycle)|date_full}}.</strong></span> + <span class="t-block t-sans">Spent by others that mentioned this candidate <strong>{{cycle_start(min_cycle)|date_full}} to {{cycle_end(cycle)|date_full}}.</strong></span> </div> </div> @@ -107,7 +107,7 @@ class="data-table data-table--heading-borders" data-type="electioneering" data-candidate="{{ candidate_id }}" - data-cycle="{{ max_cycle }}" + data-cycle="{{ cycle }}" data-election-full="{{ show_full_election }}" data-duration="{% if election_full %}{{ duration }}{% else %}2{% endif %}" > diff --git a/fec/data/templates/partials/candidate/raising.jinja b/fec/data/templates/partials/candidate/raising.jinja index 0ef63b341d..f7b89454d8 100644 --- a/fec/data/templates/partials/candidate/raising.jinja +++ b/fec/data/templates/partials/candidate/raising.jinja @@ -5,25 +5,21 @@ <div class="slab slab--inline slab--neutral u-padding--left u-padding--right"> - {{ select.candidate_cycle_select(cycles, max_cycle, id="cycle-5") }} + {{ select.candidate_cycle_select(cycles, cycle, id="cycle-5") }} {% if committee_groups['P'] or committee_groups['A']%} <span class="t-sans t-bold">Data is included from these committees:</span> <ul class="list--bulleted"> {% for committee in committee_groups['P'] | reverse %} - {% if committee.cycle == max_cycle %} <li> <a class="t-sans" href="/data/committee/{{ committee.committee_id }}/?cycle={{ committee.related_cycle }}">{{ committee.name }}</a> </li> - {% endif %} {% endfor %} {% for committee in committee_groups['A'] | reverse %} - {% if committee.cycle == max_cycle %} <li> <a class="t-sans" href="/data/committee/{{ committee.committee_id }}/?cycle={{ committee.related_cycle }}">{{ committee.name }}</a> </li> - {% endif %} {% endfor %} </ul> {% endif %} @@ -39,7 +35,7 @@ <div class="heading--section heading--with-action"> <h3 class="heading__left">Total receipts</h3> <a class="heading__right button--alt button--browse" - href="/data/receipts/?two_year_transaction_period={{ max_cycle }}{% for id in committee_ids %}&committee_id={{ id }}{% endfor %}">Filter all receipts</a> + href="/data/receipts/?two_year_transaction_period={{ cycle }}{% for id in committee_ids %}&committee_id={{ id }}{% endfor %}">Filter all receipts</a> </div> <div class="content__section--narrow"> <div class="row u-margin-bottom"> @@ -63,7 +59,7 @@ <div class="heading--section heading--with-action"> <h3 class="heading__left">Individual contributions</h3> <a class="heading__right button--alt button--browse" - href="/data/receipts/individual-contributions/?two_year_transaction_period={{ max_cycle }}&{% for id in committee_ids %}&committee_id={{ id }}{% endfor %}">Filter this data</a> + href="/data/receipts/individual-contributions/?two_year_transaction_period={{ cycle }}&{% for id in committee_ids %}&committee_id={{ id }}{% endfor %}">Filter this data</a> </div> <div class="row"> <fieldset class="toggles js-toggles"> @@ -93,7 +89,7 @@ <table class="data-table data-table--heading-borders" data-type="contributor-state" - data-cycle="{{ max_cycle }}" + data-cycle="{{ cycle }}" > <thead> <th scope="col">State</th> @@ -103,7 +99,7 @@ </div> <div class="map-panel"> - <div class="state-map" data-candidate-id="{{ candidate_id }}" data-cycle="{{ max_cycle }}"> + <div class="state-map" data-candidate-id="{{ candidate_id }}" data-cycle="{{ cycle }}"> <div class="legend-container"> <span class="t-sans t-block">By state: total amount received</span> <svg></svg> @@ -122,7 +118,7 @@ <table class="data-table data-table--heading-borders" data-type="contribution-size" - data-cycle="{{ max_cycle }}"> + data-cycle="{{ cycle }}"> <thead> <th scope="col">Contribution size</th> <th scope="col">Total contributed</th> @@ -148,7 +144,7 @@ data-candidate-id="{{ candidate_id }}" data-committee-id="{% for c in committee_groups['P'] | reverse %}{{ c.committee_id }},{% endfor %}{% for c in committee_groups['A'] | reverse %}{{ c.committee_id }},{% endfor %}" data-name="{{ name }}" - data-cycle="{{ max_cycle }}" + data-cycle="{{ cycle }}" data-duration="2" > <thead> diff --git a/fec/data/templates/partials/candidate/spending.jinja b/fec/data/templates/partials/candidate/spending.jinja index 6065fba580..b243254054 100644 --- a/fec/data/templates/partials/candidate/spending.jinja +++ b/fec/data/templates/partials/candidate/spending.jinja @@ -7,25 +7,21 @@ <div class="slab slab--inline slab--neutral u-padding--left u-padding--right"> - {{ select.candidate_cycle_select(cycles, max_cycle, id="cycle-4") }} + {{ select.candidate_cycle_select(cycles, cycle, id="cycle-4") }} {% if committee_groups['P'] or committee_groups['A']%} <span class="t-sans t-bold">Data is included from these committees:</span> <ul class="list--bulleted"> {% for committee in committee_groups['P'] | reverse %} - {% if committee.cycle == max_cycle %} <li> <a class="t-sans" href="/data/committee/{{ committee.committee_id }}/?cycle={{ committee.related_cycle }}">{{ committee.name }}</a> </li> - {% endif %} {% endfor %} {% for committee in committee_groups['A'] | reverse %} - {% if committee.cycle == max_cycle %} <li> <a class="t-sans" href="/data/committee/{{ committee.committee_id }}/?cycle={{ committee.related_cycle }}">{{ committee.name }}</a> </li> - {% endif %} {% endfor %} </ul> {% endif %} @@ -42,7 +38,7 @@ <div class="heading--section heading--with-action"> <h3 class="heading__left">Total disbursements</h3> <a class="heading__right button--alt button--browse" - href="/data/disbursements/?two_year_transaction_period={{ max_cycle }}{% for id in committee_ids %}&committee_id={{ id }}{% endfor %}">Filter all disbursements</a> + href="/data/disbursements/?two_year_transaction_period={{ cycle }}{% for id in committee_ids %}&committee_id={{ id }}{% endfor %}">Filter all disbursements</a> </div> <div class="content__section--narrow"> @@ -67,7 +63,7 @@ <div class="heading--section heading--with-action u-no-margin"> <h3 class="heading__left">Disbursement transactions</h3> <a class="heading__right button--alt button--browse" - href="/data/disbursements/?two_year_transaction_period={{ max_cycle }}{% for id in committee_ids %}&committee_id={{ id }}{% endfor %}">Filter this data</a> + href="/data/disbursements/?two_year_transaction_period={{ cycle }}{% for id in committee_ids %}&committee_id={{ id }}{% endfor %}">Filter this data</a> </div> <div class="results-info results-info--simple"> @@ -85,7 +81,7 @@ data-type="itemized-disbursements" data-committee-id="{% for id in committee_ids %}{{ id }}{% if not loop.last %},{% endif %}{% endfor %}" data-name="{{ name }}" - data-cycle="{{ max_cycle }}" + data-cycle="{{ cycle }}" data-duration="2" > <thead> diff --git a/fec/data/tests/test_candidate.py b/fec/data/tests/test_candidate.py index 20e94f14bb..b937e22d7e 100644 --- a/fec/data/tests/test_candidate.py +++ b/fec/data/tests/test_candidate.py @@ -63,8 +63,8 @@ def test_base_case(self, load_with_nested_mock, load_candidate_totals_mock, assert candidate['candidate_id'] == test_candidate['candidate_id'] assert candidate['name'] == test_candidate['name'] assert candidate['cycles'] == test_candidate['cycles'] + assert candidate['cycle'] == cycle assert candidate['min_cycle'] == cycle - 2 - assert candidate['max_cycle'] == cycle assert candidate['election_year'] == cycle assert candidate['election_years'] == test_candidate['election_years'] assert candidate['office'] == test_candidate['office'] @@ -73,8 +73,8 @@ def test_base_case(self, load_with_nested_mock, load_candidate_totals_mock, assert candidate['district'] == test_candidate['district'] assert candidate['party_full'] == test_candidate['party_full'] assert candidate['incumbent_challenge_full'] == test_candidate[ - 'incumbent_challenge_full'] - assert candidate['cycle'] == cycle + 'incumbent_challenge_full'] + assert candidate['result_type'] == 'candidates' assert candidate['duration'] == 2 assert candidate['report_type'] == 'house-senate' diff --git a/fec/data/views.py b/fec/data/views.py index 0029f68b94..c43f2b923e 100644 --- a/fec/data/views.py +++ b/fec/data/views.py @@ -179,7 +179,7 @@ def get_candidate(candidate_id, cycle, election_full): # And pass through the data processing utils aggregate = api_caller.load_candidate_totals( candidate['candidate_id'], - cycle=max_cycle, + cycle=cycle, election_full=election_full, ) if aggregate: @@ -197,7 +197,7 @@ def get_candidate(candidate_id, cycle, election_full): # raising and spending tabs two_year_totals = api_caller.load_candidate_totals( candidate['candidate_id'], - cycle=max_cycle, + cycle=cycle, election_full=False ) @@ -231,7 +231,6 @@ def get_candidate(candidate_id, cycle, election_full): 'min_cycle': min_cycle, 'report_type': report_type, 'cycles': cycles, - 'max_cycle': max_cycle, 'show_full_election': show_full_election, 'committee_groups': committee_groups, 'committees_authorized': committees_authorized, From c0279aaddfd6a0b31e9f82acc0754a1354bb078a Mon Sep 17 00:00:00 2001 From: Laura Beaufort <31420082+lbeaufort@users.noreply.github.com> Date: Tue, 28 Aug 2018 11:13:16 -0400 Subject: [PATCH 37/49] Rename min_cycle to cycle_start_year --- .../partials/candidate/other-spending-tab.jinja | 16 ++++++++-------- fec/data/tests/test_candidate.py | 3 +-- fec/data/views.py | 4 ++-- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/fec/data/templates/partials/candidate/other-spending-tab.jinja b/fec/data/templates/partials/candidate/other-spending-tab.jinja index 0e6f027f9a..0144aca056 100644 --- a/fec/data/templates/partials/candidate/other-spending-tab.jinja +++ b/fec/data/templates/partials/candidate/other-spending-tab.jinja @@ -15,18 +15,18 @@ <div class="heading--section heading--with-action"> <h3 class="heading__left">Independent expenditures</h3> <a class="heading__right button--alt button--browse" - href="/data/independent-expenditures/?min_date={{ cycle_start(min_cycle) | date(fmt='%m-%d-%Y') }}&max_date={{ cycle_end(cycle) | date(fmt='%m-%d-%Y') }}&candidate_id={{ candidate_id }}">Filter this data</a> + href="/data/independent-expenditures/?min_date={{ cycle_start(cycle_start_year) | date(fmt='%m-%d-%Y') }}&max_date={{ cycle_end(cycle) | date(fmt='%m-%d-%Y') }}&candidate_id={{ candidate_id }}">Filter this data</a> </div> <div class="results-info results-info--simple js-other-spending-totals" data-spending-type="independentExpenditures"> <div class="usa-width-one-half"> <span class="label">Support</span> <span class="t-big-data js-support"><img src="{{ static('img/loading-ellipsis.gif') }}" alt="Loading indicator"></span> - <span class="t-block t-sans">Spent by others to <strong>support</strong> from <strong>{{cycle_start(min_cycle)|date_full}} to {{cycle_end(cycle)|date_full}}.</strong></span> + <span class="t-block t-sans">Spent by others to <strong>support</strong> from <strong>{{cycle_start(cycle_start_year)|date_full}} to {{cycle_end(cycle)|date_full}}.</strong></span> </div> <div class="usa-width-one-half"> <span class="label">Oppose</span> <span class="t-big-data js-oppose"><img src="{{ static('img/loading-ellipsis.gif') }}" alt="Loading indicator"></span> - <span class="t-block t-sans">Spent by others to <strong>oppose</strong> from <strong>{{cycle_start(min_cycle)|date_full}} to {{cycle_end(cycle)|date_full}}.</strong></span> + <span class="t-block t-sans">Spent by others to <strong>oppose</strong> from <strong>{{cycle_start(cycle_start_year)|date_full}} to {{cycle_end(cycle)|date_full}}.</strong></span> </div> </div> <table @@ -56,7 +56,7 @@ {# <a class="heading__right button--alt button--browse" href="{{ url_for( 'communication_costs', - min_date=cycle_start(min_cycle) | date(fmt='%m-%d-%Y'), + min_date=cycle_start(cycle_start_year) | date(fmt='%m-%d-%Y'), max_date=cycle_end(cycle) | date(fmt='%m-%d-%Y') ) }}&candidate_id={{ candidate_id }}">Filter this data</a> #} </div> @@ -64,12 +64,12 @@ <div class="usa-width-one-half"> <span class="label">Support</span> <span class="t-big-data js-support"><img src="{{ static('img/loading-ellipsis.gif') }}" alt="Loading indicator"></span> - <span class="t-block t-sans">Spent by others to <strong>support</strong> from <strong>{{cycle_start(min_cycle)|date_full}} to {{cycle_end(cycle)|date_full}}.</strong></span> + <span class="t-block t-sans">Spent by others to <strong>support</strong> from <strong>{{cycle_start(cycle_start_year)|date_full}} to {{cycle_end(cycle)|date_full}}.</strong></span> </div> <div class="usa-width-one-half"> <span class="label">Oppose</span> <span class="t-big-data js-oppose"><img src="{{ static('img/loading-ellipsis.gif') }}" alt="Loading indicator"></span> - <span class="t-block t-sans">Spent by others to <strong>support</strong> from <strong>{{cycle_start(min_cycle)|date_full}} to {{cycle_end(cycle)|date_full}}.</strong></span> + <span class="t-block t-sans">Spent by others to <strong>support</strong> from <strong>{{cycle_start(cycle_start_year)|date_full}} to {{cycle_end(cycle)|date_full}}.</strong></span> </div> </div> <table @@ -94,12 +94,12 @@ <div class="heading--section heading--with-action u-no-margin"> <h3 class="heading__left">Electioneering communication</h3> <a class="heading__right button--alt button--browse" - href="/data/electioneering-communications/?min_date={{ cycle_start(min_cycle) | date(fmt='%m-%d-%Y') }}&max_date={{ cycle_end(cycle) | date(fmt='%m-%d-%Y') }}&candidate_id={{ candidate_id }}">Filter this data</a> + href="/data/electioneering-communications/?min_date={{ cycle_start(cycle_start_year) | date(fmt='%m-%d-%Y') }}&max_date={{ cycle_end(cycle) | date(fmt='%m-%d-%Y') }}&candidate_id={{ candidate_id }}">Filter this data</a> </div> <div class="results-info results-info--simple js-other-spending-totals" data-spending-type="electioneering"> <div class="usa-width-one-half"> <span class="t-big-data js-total-electioneering u-margin--top"><img src="{{ static('img/loading-ellipsis.gif') }}" alt="Loading indicator"></span> - <span class="t-block t-sans">Spent by others that mentioned this candidate <strong>{{cycle_start(min_cycle)|date_full}} to {{cycle_end(cycle)|date_full}}.</strong></span> + <span class="t-block t-sans">Spent by others that mentioned this candidate <strong>{{cycle_start(cycle_start_year)|date_full}} to {{cycle_end(cycle)|date_full}}.</strong></span> </div> </div> diff --git a/fec/data/tests/test_candidate.py b/fec/data/tests/test_candidate.py index b937e22d7e..210cf814c2 100644 --- a/fec/data/tests/test_candidate.py +++ b/fec/data/tests/test_candidate.py @@ -64,7 +64,7 @@ def test_base_case(self, load_with_nested_mock, load_candidate_totals_mock, assert candidate['name'] == test_candidate['name'] assert candidate['cycles'] == test_candidate['cycles'] assert candidate['cycle'] == cycle - assert candidate['min_cycle'] == cycle - 2 + assert candidate['cycle_start_year'] == cycle - 2 assert candidate['election_year'] == cycle assert candidate['election_years'] == test_candidate['election_years'] assert candidate['office'] == test_candidate['office'] @@ -74,7 +74,6 @@ def test_base_case(self, load_with_nested_mock, load_candidate_totals_mock, assert candidate['party_full'] == test_candidate['party_full'] assert candidate['incumbent_challenge_full'] == test_candidate[ 'incumbent_challenge_full'] - assert candidate['result_type'] == 'candidates' assert candidate['duration'] == 2 assert candidate['report_type'] == 'house-senate' diff --git a/fec/data/views.py b/fec/data/views.py index c43f2b923e..177691021f 100644 --- a/fec/data/views.py +++ b/fec/data/views.py @@ -126,7 +126,7 @@ def get_candidate(candidate_id, cycle, election_full): result_type = 'candidates' duration = election_durations.get(candidate['office'], 2) - min_cycle = cycle - duration if election_full else cycle + cycle_start_year = cycle - duration if election_full else cycle report_type = report_types.get(candidate['office']) # For JavaScript @@ -228,7 +228,7 @@ def get_candidate(candidate_id, cycle, election_full): 'election_years': even_election_years, 'result_type': result_type, 'duration': duration, - 'min_cycle': min_cycle, + 'cycle_start_year': cycle_start_year, 'report_type': report_type, 'cycles': cycles, 'show_full_election': show_full_election, From 73e262f3bd62a26c2bc785c4a5b6eda765acee5d Mon Sep 17 00:00:00 2001 From: john carroll <jcarroll@fec.gov> Date: Tue, 28 Aug 2018 18:14:05 -0400 Subject: [PATCH 38/49] add blank and null KWARGs to meetings-agenda streamfield --- fec/home/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fec/home/models.py b/fec/home/models.py index a083dc4172..da5b7d58ec 100644 --- a/fec/home/models.py +++ b/fec/home/models.py @@ -1023,14 +1023,14 @@ class MeetingPage(Page): agenda = StreamField([ ('agenda_item', blocks.StructBlock([ - ('item_title', blocks.TextBlock()), + ('item_title', blocks.TextBlock(required=True)), ('item_text', blocks.RichTextBlock(required=False)), ('item_audio', DocumentChooserBlock(required=False)), ('item_video', blocks.URLBlock(required=False, help_text='Add a Youtube URL to a specific\ time in a video for this agenda item')), ])) - ]) + ], blank=True, null=True) content_panels = Page.content_panels + [ FieldPanel('additional_information'), From 1265015e838e9b1844b5911838df6a800240e6fe Mon Sep 17 00:00:00 2001 From: Vraj Mohan <radhakrishnan.vrajmohan@gsa.gov> Date: Tue, 28 Aug 2018 15:37:12 -0700 Subject: [PATCH 39/49] Remove unused code --- fec/data/utils.py | 59 ----------------------------------------------- 1 file changed, 59 deletions(-) diff --git a/fec/data/utils.py b/fec/data/utils.py index b6f296584e..aab9d0b646 100644 --- a/fec/data/utils.py +++ b/fec/data/utils.py @@ -1,10 +1,6 @@ import math import calendar import datetime -import threading - -import cachetools -import cachecontrol from data import constants @@ -13,61 +9,6 @@ def current_cycle(): year = datetime.datetime.now().year return year + year % 2 -class LRUCache(cachecontrol.cache.BaseCache): - """A thread-safe least recently updated cache adapted to work with - Cache-Control. - """ - def __init__(self, maxsize): - self.lock = threading.Lock() - self.data = cachetools.LRUCache(maxsize) - - def get(self, key): - return self.data.get(key, None) - - def set(self, key, value): - with self.lock: - self.data[key] = value - - def delete(self, key): - with self.lock: - self.data.clear() - - -class ReverseProxied(object): - """Wrap the application in this middleware and configure the - front-end server to add these headers, to let you quietly bind - this to a URL other than / and to an HTTP scheme that is - different than what is used locally. - - From http://flask.pocoo.org/snippets/35/. - - In nginx: - location /myprefix { - proxy_pass http://192.168.0.1:5001; - proxy_set_header Host $host; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Scheme $scheme; - proxy_set_header X-Script-Name /myprefix; - } - - :param app: the WSGI application - """ - def __init__(self, app): - self.app = app - - def __call__(self, environ, start_response): - script_name = environ.get('HTTP_X_SCRIPT_NAME', '') - if script_name: - environ['SCRIPT_NAME'] = script_name - path_info = environ['PATH_INFO'] - if path_info.startswith(script_name): - environ['PATH_INFO'] = path_info[len(script_name):] - - scheme = environ.get('HTTP_X_SCHEME', '') - if scheme: - environ['wsgi.url_scheme'] = scheme - return self.app(environ, start_response) - def date_ranges(): """Build date ranges for current day, month, quarter, and year. From 5c49a5ffd7e4f886331059024fdca41fc2550f70 Mon Sep 17 00:00:00 2001 From: Vraj Mohan <radhakrishnan.vrajmohan@gsa.gov> Date: Tue, 28 Aug 2018 13:56:33 -0700 Subject: [PATCH 40/49] Format code to conform to PEP8 --- fec/data/tests/test_utils.py | 42 ++++++++++++++++++++---------------- fec/data/utils.py | 8 ++++--- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/fec/data/tests/test_utils.py b/fec/data/tests/test_utils.py index 7ece0ef6d1..ff6cdb1fc3 100644 --- a/fec/data/tests/test_utils.py +++ b/fec/data/tests/test_utils.py @@ -1,6 +1,4 @@ import unittest -import datetime -import locale from collections import OrderedDict from django.test import TestCase @@ -10,38 +8,46 @@ import data.utils as utils import data.api_caller as api_caller + class TestUtils(TestCase): def test_currency_filter_not_none(self): assert filters.currency(1.05) == '$1.05' - def test_currency_filter_none(self): assert filters.currency(None) is None - def test_fmt_year_range_int(self): assert filters.fmt_year_range(1985) == '1984–1985' - def test_fmt_year_range_not_int(self): assert filters.fmt_year_range('1985') is None assert filters.fmt_year_range(None) is None - def test_fmt_state_full(self): value = 'ny' assert filters.fmt_state_full(value) == 'New York' - def test_election_url(self): - candidate = {'office': 'P', 'office_full': 'President', 'state': 'US', 'district': None} - assert filters.get_election_url(candidate, 2012) == '/data/elections/president/2012' - candidate = {'office': 'S', 'office_full': 'Senate', 'state': 'NJ', 'district': None} - assert filters.get_election_url(candidate, 2012) == '/data/elections/senate/NJ/2012' - candidate = {'office': 'S', 'office_full': 'Senate', 'state': 'NJ', 'district': '00'} - assert filters.get_election_url(candidate, 2012) == '/data/elections/senate/NJ/2012' - candidate = {'office': 'H', 'office_full': 'House', 'state': 'NJ', 'district': '02'} - assert filters.get_election_url(candidate, 2012) == '/data/elections/house/NJ/02/2012' + candidate = { + 'office': 'P', 'office_full': 'President', 'state': 'US', + 'district': None} + assert (filters.get_election_url(candidate, 2012) + == '/data/elections/president/2012') + candidate = { + 'office': 'S', 'office_full': 'Senate', 'state': 'NJ', + 'district': None} + assert (filters.get_election_url(candidate, 2012) + == '/data/elections/senate/NJ/2012') + candidate = { + 'office': 'S', 'office_full': 'Senate', 'state': 'NJ', + 'district': '00'} + assert (filters.get_election_url(candidate, 2012) + == '/data/elections/senate/NJ/2012') + candidate = { + 'office': 'H', 'office_full': 'House', 'state': 'NJ', + 'district': '02'} + assert (filters.get_election_url(candidate, 2012) + == '/data/elections/house/NJ/02/2012') def test_financial_summary_processor(self): totals = { @@ -52,7 +58,9 @@ def test_financial_summary_processor(self): ('receipts', ('Total receipts', '1')), ('disbursements', ('Total disbursements', '1')) ]) - assert utils.financial_summary_processor(totals, formatter) == [(200, ('Total receipts', '1')), (100, ('Total disbursements', '1'))] + assert utils.financial_summary_processor(totals, formatter) == [ + (200, ('Total receipts', '1')), (100, ('Total disbursements', '1')) + ] def current_cycle(self): return 2016 @@ -93,7 +101,6 @@ def test_get_senate_cycles(self): assert utils.get_senate_cycles('3', current_cycle) == range( 2022, 1979, -6) - def test_state_senate_cycles(self): # Testing mock result from call_senate_specials() @@ -124,4 +131,3 @@ def test_state_senate_cycles(self): wisconsin = api_caller.get_regular_senate_cycles('wi') assert 2016 in wisconsin assert 2014 not in wisconsin - \ No newline at end of file diff --git a/fec/data/utils.py b/fec/data/utils.py index aab9d0b646..498d36a464 100644 --- a/fec/data/utils.py +++ b/fec/data/utils.py @@ -75,7 +75,8 @@ def page_info(pagination): count = '{:,}'.format(pagination['count']) range_start = per_page * (page - 1) + 1 range_end = (page - 1) * 10 + per_page - return '{range_start}-{range_end} of {count}'.format(range_start=range_start, range_end=range_end, count=count) + return '{range_start}-{range_end} of {count}'.format( + range_start=range_start, range_end=range_end, count=count) def financial_summary_processor(totals, formatter): @@ -92,8 +93,9 @@ def financial_summary_processor(totals, formatter): def process_raising_data(totals): """ Processes raising totals by mapping to the RAISING_FORMATTER constant - Occassionally, the API schema is slightly out of sync with what we want to display, - so there's logic here to remove or rename items depending on the form we're showing + Occasionally, the API schema is slightly out of sync with what we want to + display, so there's logic here to remove or rename items depending on the + form we're showing """ # If there's repayments_loans_made_by_candidate, it's an F3P . From 6b1873614c29129fe212c92f1094c91e9d2cbfe3 Mon Sep 17 00:00:00 2001 From: Vraj Mohan <radhakrishnan.vrajmohan@gsa.gov> Date: Tue, 28 Aug 2018 15:41:36 -0700 Subject: [PATCH 41/49] Add test for election_title --- fec/data/tests/test_utils.py | 9 +++++++++ fec/data/utils.py | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/fec/data/tests/test_utils.py b/fec/data/tests/test_utils.py index ff6cdb1fc3..f47d871ddb 100644 --- a/fec/data/tests/test_utils.py +++ b/fec/data/tests/test_utils.py @@ -6,6 +6,7 @@ from data.templatetags import filters import data.utils as utils +from data.utils import election_title import data.api_caller as api_caller @@ -49,6 +50,14 @@ def test_election_url(self): assert (filters.get_election_url(candidate, 2012) == '/data/elections/house/NJ/02/2012') + def test_election_title(self): + assert election_title(2016, 'President') == ( + '2016 Election United States President') + assert election_title(2016, 'Senate', state='CA') == ( + '2016 Election United States Senate - California') + assert election_title(2016, 'House', state='CA', district='08') == ( + '2016 Election United States House - California - District 08') + def test_financial_summary_processor(self): totals = { 'receipts': 200, diff --git a/fec/data/utils.py b/fec/data/utils.py index 498d36a464..b8de9c3ec3 100644 --- a/fec/data/utils.py +++ b/fec/data/utils.py @@ -57,7 +57,8 @@ def get_cycles(max_cycle=None): def election_title(cycle, office, state=None, district=None): # If this logic changes, please update `page-header.jinja` logic - base = ' '.join([str(cycle), 'Election', 'United States', office.capitalize()]) + base = ' '.join( + [str(cycle), 'Election', 'United States', office.capitalize()]) parts = [base] if state: parts.append(constants.states[state.upper()]) From e341891f9596c0e0762b733b326978ddb71adaca Mon Sep 17 00:00:00 2001 From: Laura Beaufort <31420082+lbeaufort@users.noreply.github.com> Date: Wed, 29 Aug 2018 10:38:22 -0400 Subject: [PATCH 42/49] Simplify get_candidate logic --- fec/data/tests/test_candidate.py | 10 +-- fec/data/views.py | 111 ++++++++++++++++--------------- 2 files changed, 58 insertions(+), 63 deletions(-) diff --git a/fec/data/tests/test_candidate.py b/fec/data/tests/test_candidate.py index 210cf814c2..a4df34e87e 100644 --- a/fec/data/tests/test_candidate.py +++ b/fec/data/tests/test_candidate.py @@ -16,6 +16,7 @@ class TestCandidate(TestCase): "name": "Lady Liberty", "cycles": [2014, 2016, 2018], "election_years": [2016, 2018], + "two_year_period": 2016, "election_districts": ["01", "02"], "office": "H", "office_full": "House", @@ -103,14 +104,7 @@ def test_base_case(self, load_with_nested_mock, load_candidate_totals_mock, 'committee_id': 'C003'}] } - assert candidate['committees_authorized'] == ( - candidate['committee_groups']['P'] + - candidate['committee_groups']['A']) - assert candidate['committee_ids'] == [ - committee['committee_id'] - for committee in candidate['committees_authorized'] - ] - + assert candidate['committee_ids'] == ['C001', 'C002'] assert candidate['elections'] == [(2018, '02'), (2016, '01')] assert candidate['candidate'] == test_candidate assert candidate['context_vars'] == { diff --git a/fec/data/views.py b/fec/data/views.py index 177691021f..8f9228a607 100644 --- a/fec/data/views.py +++ b/fec/data/views.py @@ -84,12 +84,25 @@ def advanced(request): }) +def cycle_in_range(cycle): + try: + return cycle <= utils.current_cycle() + except: + return False + + def get_candidate(candidate_id, cycle, election_full): + show_full_election = election_full if cycle_in_range(cycle) else False + + if not cycle_in_range(cycle): + cycle = utils.current_cycle() + + # Get candidate/committee info and latest cycle candidate, committees, cycle = api_caller.load_with_nested( 'candidate', candidate_id, 'committees', cycle=cycle, cycle_key='two_year_period', - election_full=election_full, + election_full=show_full_election, ) # cycle corresponds to the two-year period for which the committee has @@ -123,97 +136,86 @@ def get_candidate(candidate_id, cycle, election_full): if year >= cycle), None, ) + # Round odd year special elections for election dropdowns + # Use Set to ensure no duplicate years + even_election_years = list( + {year + (year % 2) for year in candidate.get('election_years', [])} + ) - result_type = 'candidates' - duration = election_durations.get(candidate['office'], 2) - cycle_start_year = cycle - duration if election_full else cycle + cycle_duration = election_durations.get(candidate['office'], 2) + cycle_start_year = cycle - cycle_duration if show_full_election else cycle report_type = report_types.get(candidate['office']) - # For JavaScript - context_vars = { - 'cycles': candidate['cycles'], - 'name': candidate['name'], - 'cycle': cycle, - 'electionFull': election_full, - 'candidateID': candidate['candidate_id'] - } - - # Addresses issue#1644 - make any odd year special election an even year - # for displaying elections for pulldown menu in Candidate pages - # Using Set to ensure no duplicate years in final list - even_election_years = list( - {year + (year % 2) for year in candidate.get('election_years', [])}) - - # In the case of when a presidential or senate candidate has filed - # for a future year that's beyond the current cycle, - # set a max_cycle var to the current cycle we're in - # and when calling the API for totals, set election_full to False. - # The max_cycle value is also referenced in the templates for setting - # the cycle for itemized tables. Because these are only in 2-year chunks, + # Because the API only has totals through the current cycle, # the cycle should never be beyond the one we're in. - cycles = [cycle for cycle in candidate['cycles'] if cycle <= utils.current_cycle()] - max_cycle = cycle if cycle <= utils.current_cycle() else utils.current_cycle() - show_full_election = election_full if cycle <= utils.current_cycle() else False + cycles = [ + cycle_tmp for cycle_tmp in candidate['cycles'] + if cycle_in_range(cycle_tmp) + ] # Annotate committees with most recent available cycle aggregate_cycles = ( - list(range(cycle, cycle - duration, -2)) - if election_full + list(range(cycle, cycle - cycle_duration, -2)) + if show_full_election else [cycle] ) for committee in committees: committee['related_cycle'] = ( max(cycle for cycle in aggregate_cycles if cycle in committee['cycles']) - if election_full + if show_full_election else candidate['two_year_period'] ) # Group the committees by designation committee_groups = groupby(committees, lambda each: each['designation']) - committees_authorized = committee_groups.get('P', []) + committee_groups.get('A', []) - - committee_ids = [committee['committee_id'] for committee in committees_authorized] + authorized_committee_ids = [ + committee['committee_id'] for committee in committees + if committee.get('designation') in ('P', 'A') + ] - # Get aggregate totals for the financial summary - # And pass through the data processing utils - aggregate = api_caller.load_candidate_totals( + # Get aggregate totals and process for financial summary + aggregate_totals = api_caller.load_candidate_totals( candidate['candidate_id'], cycle=cycle, - election_full=election_full, + election_full=show_full_election, ) - if aggregate: - raising_summary = utils.process_raising_data(aggregate) - spending_summary = utils.process_spending_data(aggregate) - cash_summary = utils.process_cash_data(aggregate) + if aggregate_totals: + raising_summary = utils.process_raising_data(aggregate_totals) + spending_summary = utils.process_spending_data(aggregate_totals) + cash_summary = utils.process_cash_data(aggregate_totals) else: raising_summary = None spending_summary = None cash_summary = None - aggregate = aggregate - - # Get totals for the last two-year period of a cycle for showing on - # raising and spending tabs + # Get totals for the last two-year period for raising and spending tabs two_year_totals = api_caller.load_candidate_totals( candidate['candidate_id'], cycle=cycle, election_full=False ) - # Get the statements of candidacy statement_of_candidacy = api_caller.load_candidate_statement_of_candidacy( candidate['candidate_id'], cycle=cycle ) - # Get all the elections elections = sorted( zip(candidate['election_years'], candidate['election_districts']), key=lambda pair: pair[0], reverse=True, ) + # For JavaScript + context_vars = { + 'cycles': candidate['cycles'], + 'name': candidate['name'], + 'cycle': cycle, + 'electionFull': show_full_election, + 'candidateID': candidate['candidate_id'] + } + return { 'name': candidate['name'], 'cycle': int(cycle), @@ -226,19 +228,19 @@ def get_candidate(candidate_id, cycle, election_full): 'incumbent_challenge_full': candidate['incumbent_challenge_full'], 'election_year': election_year, 'election_years': even_election_years, - 'result_type': result_type, - 'duration': duration, + 'result_type': 'candidates', + 'duration': cycle_duration, 'cycle_start_year': cycle_start_year, + 'report_type': report_type, 'cycles': cycles, 'show_full_election': show_full_election, 'committee_groups': committee_groups, - 'committees_authorized': committees_authorized, - 'committee_ids': committee_ids, + 'committee_ids': authorized_committee_ids, 'raising_summary': raising_summary, 'spending_summary': spending_summary, 'cash_summary': cash_summary, - 'aggregate': aggregate, + 'aggregate': aggregate_totals, 'two_year_totals': two_year_totals, 'statement_of_candidacy': statement_of_candidacy, 'elections': elections, @@ -252,8 +254,7 @@ def candidate(request, candidate_id): if cycle is not None: cycle = int(cycle) - election_full = request.GET.get('election_full', True) - election_full = bool(strtobool(str(election_full))) + election_full = bool(strtobool(request.GET.get('election_full', 'True'))) candidate = get_candidate(candidate_id, cycle, election_full) return render(request, 'candidates-single.jinja', candidate) From 8bf584d44909e862355bb30afaa28e25fc3c9eef Mon Sep 17 00:00:00 2001 From: Laura Beaufort <31420082+lbeaufort@users.noreply.github.com> Date: Wed, 29 Aug 2018 10:38:55 -0400 Subject: [PATCH 43/49] Remove unnecessary logic causing 404 errors This change originally came from PR https://github.com/18F/openFEC-web-app/pull/1945. After much investigation, I can't see any use case for this logic given the current setup, so I'm removing it. The user interface ensures on the "financial summary", "about this candidate", and "spending by other tabs", where the cycle options correspond with election_years, election_year/cycle will always be a valid election year. --- fec/data/views.py | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/fec/data/views.py b/fec/data/views.py index 8f9228a607..d629c5736b 100644 --- a/fec/data/views.py +++ b/fec/data/views.py @@ -105,32 +105,6 @@ def get_candidate(candidate_id, cycle, election_full): election_full=show_full_election, ) - # cycle corresponds to the two-year period for which the committee has - # financial activity. when selected election cycle is not in list of - # election years, get the next election cycle - if election_full and cycle and cycle not in candidate['election_years']: - - next_cycle = next( - ( - year for year in sorted(candidate['election_years']) - if year > cycle - ), - max(candidate['election_years']), - ) - - # If the next_cycle is odd set it to whatever the cycle value was- - # falls back to the cycle and then set election_full to false - # This solves issue# 1945 with odd year special elections - if next_cycle % 2 > 0: - next_cycle = cycle - election_full = False - # get the next election cycle data for this candidate - candidate, committees, cycle = api_caller.load_with_nested( - 'candidate', candidate_id, 'committees', - cycle=next_cycle, cycle_key='two_year_period', - election_full=election_full, - ) - election_year = next( (year for year in sorted(candidate['election_years']) if year >= cycle), From f7fb4e9ec32fe2532c5e5a0f655dbda87c2bbaf0 Mon Sep 17 00:00:00 2001 From: Laura Beaufort <31420082+lbeaufort@users.noreply.github.com> Date: Wed, 29 Aug 2018 09:33:15 -0400 Subject: [PATCH 44/49] Replace committee.related_cycle with view cycle The max() function in "Annotate committees with most recent available cycle" was throwing errors - this simplifies the logic --- .../partials/candidate/about-candidate.jinja | 6 +++--- .../partials/candidate/financial-summary.jinja | 4 ++-- .../templates/partials/candidate/raising.jinja | 4 ++-- .../templates/partials/candidate/spending.jinja | 4 ++-- fec/data/tests/test_candidate.py | 3 --- fec/data/views.py | 15 --------------- 6 files changed, 9 insertions(+), 27 deletions(-) diff --git a/fec/data/templates/partials/candidate/about-candidate.jinja b/fec/data/templates/partials/candidate/about-candidate.jinja index cf5cae924f..de736dc37a 100644 --- a/fec/data/templates/partials/candidate/about-candidate.jinja +++ b/fec/data/templates/partials/candidate/about-candidate.jinja @@ -84,7 +84,7 @@ <div class="grid__item u-no-margin"> <div class="callout callout--primary{% if loop.last %} u-no-margin{% endif %}" style="width: 100%; max-width: 300px"> <h5 class="callout__title t-sans"> - <a href="/data/committee/{{ committee.committee_id }}/?cycle={{ committee.related_cycle }}">{{ committee.name }}</a> + <a href="/data/committee/{{ committee.committee_id }}/?cycle={{ cycle }}">{{ committee.name }}</a> </h5> <span class="callout__subtitle term" data-term="principal campaign committee">Principal campaign committee</span> </div> @@ -98,7 +98,7 @@ <ul class="list--bulleted" style="margin-top: 1rem"> {% for committee in committee_groups['A'] | reverse %} <li class="u-no-margin-bottom"> - <a class="t-sans" href="/data/committee/{{ committee.committee_id }}/?cycle={{ committee.related_cycle }}">{{ committee.name }}</a> + <a class="t-sans" href="/data/committee/{{ committee.committee_id }}/?cycle={{ cycle }}">{{ committee.name }}</a> </li> {% endfor %} </ul> @@ -119,7 +119,7 @@ <ul class="list--bulleted"> {% for committee in committee_groups['J'] | reverse %} <li> - <a class="t-sans" href="/data/committee/{{ committee.committee_id }}/?cycle={{ committee.related_cycle }}">{{ committee.name }}</a> + <a class="t-sans" href="/data/committee/{{ committee.committee_id }}/?cycle={{ cycle }}">{{ committee.name }}</a> </li> {% endfor %} </ul> diff --git a/fec/data/templates/partials/candidate/financial-summary.jinja b/fec/data/templates/partials/candidate/financial-summary.jinja index a1dd45526a..232cba7261 100644 --- a/fec/data/templates/partials/candidate/financial-summary.jinja +++ b/fec/data/templates/partials/candidate/financial-summary.jinja @@ -14,12 +14,12 @@ <ul class="list--bulleted"> {% for committee in committee_groups['P'] | reverse %} <li> - <a class="t-sans" href="/data/committee/{{ committee.committee_id }}/?cycle={{ committee.related_cycle }}">{{ committee.name }}</a> + <a class="t-sans" href="/data/committee/{{ committee.committee_id }}/?cycle={{ cycle }}">{{ committee.name }}</a> </li> {% endfor %} {% for committee in committee_groups['A'] | reverse %} <li> - <a class="t-sans" href="/data/committee/{{ committee.committee_id }}/?cycle={{ committee.related_cycle }}">{{ committee.name }}</a> + <a class="t-sans" href="/data/committee/{{ committee.committee_id }}/?cycle={{ cycle }}">{{ committee.name }}</a> </li> {% endfor %} </ul> diff --git a/fec/data/templates/partials/candidate/raising.jinja b/fec/data/templates/partials/candidate/raising.jinja index f7b89454d8..b4c2124f01 100644 --- a/fec/data/templates/partials/candidate/raising.jinja +++ b/fec/data/templates/partials/candidate/raising.jinja @@ -13,12 +13,12 @@ <ul class="list--bulleted"> {% for committee in committee_groups['P'] | reverse %} <li> - <a class="t-sans" href="/data/committee/{{ committee.committee_id }}/?cycle={{ committee.related_cycle }}">{{ committee.name }}</a> + <a class="t-sans" href="/data/committee/{{ committee.committee_id }}/?cycle={{ cycle }}">{{ committee.name }}</a> </li> {% endfor %} {% for committee in committee_groups['A'] | reverse %} <li> - <a class="t-sans" href="/data/committee/{{ committee.committee_id }}/?cycle={{ committee.related_cycle }}">{{ committee.name }}</a> + <a class="t-sans" href="/data/committee/{{ committee.committee_id }}/?cycle={{ cycle }}">{{ committee.name }}</a> </li> {% endfor %} </ul> diff --git a/fec/data/templates/partials/candidate/spending.jinja b/fec/data/templates/partials/candidate/spending.jinja index b243254054..7783883d94 100644 --- a/fec/data/templates/partials/candidate/spending.jinja +++ b/fec/data/templates/partials/candidate/spending.jinja @@ -15,12 +15,12 @@ <ul class="list--bulleted"> {% for committee in committee_groups['P'] | reverse %} <li> - <a class="t-sans" href="/data/committee/{{ committee.committee_id }}/?cycle={{ committee.related_cycle }}">{{ committee.name }}</a> + <a class="t-sans" href="/data/committee/{{ committee.committee_id }}/?cycle={{ cycle }}">{{ committee.name }}</a> </li> {% endfor %} {% for committee in committee_groups['A'] | reverse %} <li> - <a class="t-sans" href="/data/committee/{{ committee.committee_id }}/?cycle={{ committee.related_cycle }}">{{ committee.name }}</a> + <a class="t-sans" href="/data/committee/{{ committee.committee_id }}/?cycle={{ cycle }}">{{ committee.name }}</a> </li> {% endfor %} </ul> diff --git a/fec/data/tests/test_candidate.py b/fec/data/tests/test_candidate.py index a4df34e87e..51e0f73a6e 100644 --- a/fec/data/tests/test_candidate.py +++ b/fec/data/tests/test_candidate.py @@ -86,21 +86,18 @@ def test_base_case(self, load_with_nested_mock, load_candidate_totals_mock, 'cycles': [2016, 2014, 2012, 2018], 'name': 'My Primary Campaign Committee', 'cycle': 2016, - 'related_cycle': 2016, 'committee_id': 'C001'}], 'A': [{ 'designation': 'A', 'cycles': [2016, 2014, 2012, 2018], 'name': 'My Authorized Campaign Committee', 'cycle': 2016, - 'related_cycle': 2016, 'committee_id': 'C002'}], 'J': [{ 'designation': 'J', 'cycles': [2016, 2014, 2012, 2018], 'name': 'Joint Fundraising Committee', 'cycle': 2016, - 'related_cycle': 2016, 'committee_id': 'C003'}] } diff --git a/fec/data/views.py b/fec/data/views.py index d629c5736b..fbf65ecfcb 100644 --- a/fec/data/views.py +++ b/fec/data/views.py @@ -127,20 +127,6 @@ def get_candidate(candidate_id, cycle, election_full): if cycle_in_range(cycle_tmp) ] - # Annotate committees with most recent available cycle - aggregate_cycles = ( - list(range(cycle, cycle - cycle_duration, -2)) - if show_full_election - else [cycle] - ) - for committee in committees: - committee['related_cycle'] = ( - max(cycle for cycle in aggregate_cycles - if cycle in committee['cycles']) - if show_full_election - else candidate['two_year_period'] - ) - # Group the committees by designation committee_groups = groupby(committees, lambda each: each['designation']) authorized_committee_ids = [ @@ -205,7 +191,6 @@ def get_candidate(candidate_id, cycle, election_full): 'result_type': 'candidates', 'duration': cycle_duration, 'cycle_start_year': cycle_start_year, - 'report_type': report_type, 'cycles': cycles, 'show_full_election': show_full_election, From 32abdd3f7a6ddebc8ccc55f4a697b8a779c6c4e9 Mon Sep 17 00:00:00 2001 From: Laura Beaufort <31420082+lbeaufort@users.noreply.github.com> Date: Wed, 22 Aug 2018 13:06:19 -0400 Subject: [PATCH 45/49] Simplify candidate/committee 2-year-select macros Allow two_year_select to be passed election_full values for candidate pages --- fec/data/templates/macros/cycle-select.jinja | 19 ++----------------- .../partials/candidate/raising.jinja | 2 +- .../partials/candidate/spending.jinja | 2 +- .../partials/committee/about-committee.jinja | 2 +- .../partials/committee/filings.jinja | 2 +- .../committee/financial-summary.jinja | 2 +- .../partials/committee/raising.jinja | 2 +- .../partials/committee/spending.jinja | 2 +- 8 files changed, 9 insertions(+), 24 deletions(-) diff --git a/fec/data/templates/macros/cycle-select.jinja b/fec/data/templates/macros/cycle-select.jinja index 9404c3001e..0b9037102c 100644 --- a/fec/data/templates/macros/cycle-select.jinja +++ b/fec/data/templates/macros/cycle-select.jinja @@ -27,27 +27,12 @@ {% endif %} {% endmacro %} -{% macro candidate_cycle_select(cycles, cycle=none, id='') %} -{% set cycle = cycle | int %} - <div class="content__section"> - <label for="{{id}}-cycle" class="label cycle-select__label">Election</label> - <select id="{{id}}-cycle" class="js-cycle" name="cycle" data-cycle-location="query" data-election-full="False"> - {% for each in cycles | sort(reverse=True) %} - <option - value="{{ each }}" - {% if cycle and cycle <= each and cycle > (each - 2) %}selected{% endif %} - >{{ each }}</option> - {% endfor %} - </select> - </div> -{% endmacro %} - -{% macro committee_cycle_select(cycles, cycle=none, id='') %} +{% macro two_year_select(cycles, cycle=none, id='', election_full='') %} {% set cycle = cycle | int %} <div class="row content__section"> <div class="cycle-select"> <label for="{{id}}-cycle" class="label cycle-select__label">Two-year period</label> - <select id="{{id}}-cycle" class="js-cycle" name="cycle" data-cycle-location="query"> + <select id="{{id}}-cycle" class="js-cycle" name="cycle" data-cycle-location="query" {% if election_full%}data-election-full="{{election_full}}"{%endif%}> {% for each in cycles | sort(reverse=True) %} <option value="{{ each }}" diff --git a/fec/data/templates/partials/candidate/raising.jinja b/fec/data/templates/partials/candidate/raising.jinja index b4c2124f01..e2cd2c2f5a 100644 --- a/fec/data/templates/partials/candidate/raising.jinja +++ b/fec/data/templates/partials/candidate/raising.jinja @@ -5,7 +5,7 @@ <div class="slab slab--inline slab--neutral u-padding--left u-padding--right"> - {{ select.candidate_cycle_select(cycles, cycle, id="cycle-5") }} + {{ select.two_year_select(cycles, cycle, id="cycle-5", election_full="false") }} {% if committee_groups['P'] or committee_groups['A']%} <span class="t-sans t-bold">Data is included from these committees:</span> diff --git a/fec/data/templates/partials/candidate/spending.jinja b/fec/data/templates/partials/candidate/spending.jinja index 7783883d94..2d54b5f79a 100644 --- a/fec/data/templates/partials/candidate/spending.jinja +++ b/fec/data/templates/partials/candidate/spending.jinja @@ -7,7 +7,7 @@ <div class="slab slab--inline slab--neutral u-padding--left u-padding--right"> - {{ select.candidate_cycle_select(cycles, cycle, id="cycle-4") }} + {{ select.two_year_select(cycles, cycle, id="cycle-4", election_full="false") }} {% if committee_groups['P'] or committee_groups['A']%} <span class="t-sans t-bold">Data is included from these committees:</span> diff --git a/fec/data/templates/partials/committee/about-committee.jinja b/fec/data/templates/partials/committee/about-committee.jinja index 892fb0cdba..63f9c04029 100644 --- a/fec/data/templates/partials/committee/about-committee.jinja +++ b/fec/data/templates/partials/committee/about-committee.jinja @@ -3,7 +3,7 @@ <section id="section-2" role="tabpanel" aria-hidden="true" aria-labelledby="section-2-heading"> <h2 id="section-2-heading">About this committee</h2> <div class="slab slab--inline slab--neutral u-padding--left u-padding--right"> - {{ select.committee_cycle_select(cycles, cycle, 'about')}} + {{ select.two_year_select(cycles, cycle, 'about')}} <div class="entity__figure row"> <h3 class="heading--section">Committee information</h3> <table class="t-sans usa-width-three-fourths"> diff --git a/fec/data/templates/partials/committee/filings.jinja b/fec/data/templates/partials/committee/filings.jinja index 12ef60a08a..8907924b43 100644 --- a/fec/data/templates/partials/committee/filings.jinja +++ b/fec/data/templates/partials/committee/filings.jinja @@ -6,7 +6,7 @@ Committee filings </h2> <div class="slab slab--inline slab--neutral u-padding--left u-padding--right"> - {{ select.committee_cycle_select(cycles, cycle, 'filings')}} + {{ select.two_year_select(cycles, cycle, 'filings')}} {% if has_raw_filings %} {{ entity.raw_filings_table(cycle, committee_id, min_receipt_date) }} {% endif %} diff --git a/fec/data/templates/partials/committee/financial-summary.jinja b/fec/data/templates/partials/committee/financial-summary.jinja index f1f12ed658..4195d1b997 100644 --- a/fec/data/templates/partials/committee/financial-summary.jinja +++ b/fec/data/templates/partials/committee/financial-summary.jinja @@ -5,7 +5,7 @@ <section id="section-1" role="tabpanel" aria-hidden="true" aria-labelledby="section-1-heading"> <h2 id="section-1-heading">Financial summary</h2> <div class="slab slab--inline slab--neutral u-padding--left u-padding--right"> - {{ select.committee_cycle_select(cycles, cycle, 'summary')}} + {{ select.two_year_select(cycles, cycle, 'summary')}} {% if reports and totals %} {% if committee_type == 'I' %} <div class="entity__figure"> diff --git a/fec/data/templates/partials/committee/raising.jinja b/fec/data/templates/partials/committee/raising.jinja index 67dd9727f0..026db8b838 100644 --- a/fec/data/templates/partials/committee/raising.jinja +++ b/fec/data/templates/partials/committee/raising.jinja @@ -13,7 +13,7 @@ <section id="section-3" role="tabpanel" aria-hidden="true" aria-labelledby="section-3-heading"> <h2 id="section-3-heading">Raising</h2> <div class="slab slab--inline slab--neutral u-padding--left u-padding--right"> - {{ select.committee_cycle_select(cycles, cycle, 'receipts')}} + {{ select.two_year_select(cycles, cycle, 'receipts')}} {% if totals and committee_type not in ['C', 'E', 'I'] %} <div id="total-receipts" class="entity__figure row"> <div class="heading--section heading--with-action"> diff --git a/fec/data/templates/partials/committee/spending.jinja b/fec/data/templates/partials/committee/spending.jinja index 2ab43efceb..b28bce5237 100644 --- a/fec/data/templates/partials/committee/spending.jinja +++ b/fec/data/templates/partials/committee/spending.jinja @@ -12,7 +12,7 @@ <section id="section-4" role="tabpanel" aria-hidden="true" aria-labelledby="section-4-heading"> <h2 id="section-4-heading">Spending</h2> <div class="slab slab--inline slab--neutral u-padding--left u-padding--right"> - {{ select.committee_cycle_select(cycles, cycle, 'disbursements')}} + {{ select.two_year_select(cycles, cycle, 'disbursements')}} {% if totals and committee_type not in ['C', 'E', 'I'] %} <div id="total-disbursements" class="entity__figure row"> From 8bfb8a8ca1f25fbbd8082fb415b55b819af40cf3 Mon Sep 17 00:00:00 2001 From: Vraj Mohan <radhakrishnan.vrajmohan@gsa.gov> Date: Wed, 29 Aug 2018 18:03:18 -0700 Subject: [PATCH 46/49] Format code for PEP8 --- fec/data/tests/test_legal_search.py | 42 ++++++++++++++++------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/fec/data/tests/test_legal_search.py b/fec/data/tests/test_legal_search.py index d184167d79..d54862fd81 100644 --- a/fec/data/tests/test_legal_search.py +++ b/fec/data/tests/test_legal_search.py @@ -1,10 +1,8 @@ -import unittest import datetime +from unittest import mock from django.test import Client from django.test import TestCase -from unittest import mock -from urllib.parse import urlparse, parse_qs from data import api_caller from data import legal_test_data @@ -17,7 +15,7 @@ class TestLegalSearch(unittest.TestCase): @mock.patch.object(api_caller, 'load_legal_search_results') def test_no_query(self, load_legal_search_results): response = client.get('/data/legal/search/') - assert response.status_code == 200 + assert response.status_code == 200 load_legal_search_results.assert_not_called() # Test2 : A new issue is opened https://github.com/18F/fec-cms/issues/1477 @@ -42,13 +40,15 @@ def test_no_query(self, load_legal_search_results): # Test3 : OK @mock.patch.object(api_caller, 'load_legal_search_results') def test_search_universal(self, load_legal_search_results): - load_legal_search_results.return_value = legal_test_data.legal_universal_search_results() + load_legal_search_results.return_value =\ + legal_test_data.legal_universal_search_results() response = client.get('/data/legal/search/', data={ 'search': 'in kind donation', 'search_type': 'all'}) assert response.status_code == 200 - load_legal_search_results.assert_called_once_with('in kind donation', 'all', limit=3) + load_legal_search_results.assert_called_once_with( + 'in kind donation', 'all', limit=3) # Test4 : This test is checking against the static data on legal_test_data.py. # offset value is 3 and not 0. revisit the test @@ -56,34 +56,37 @@ def test_search_universal(self, load_legal_search_results): # Actual call: load_legal_search_results('in kind donation', 'regulations', limit=3) @mock.patch.object(api_caller, 'load_legal_search_results') def test_search_regulations(self, load_legal_search_results): - load_legal_search_results.return_value = legal_test_data.regulations_search_results() + load_legal_search_results.return_value =\ + legal_test_data.regulations_search_results() response = client.get('/data/legal/search/regulations/', data={ 'search': 'in kind donation', 'search_type': 'regulations'}) assert response.status_code == 200 - load_legal_search_results.assert_called_once_with('in kind donation', - 'regulations', offset=0) + load_legal_search_results.assert_called_once_with( + 'in kind donation', 'regulations', offset=0) # Test5 : OK. This test works only if offset value is set - # as a string and not integer in the assert_called_once_with. + # as a string and not integer in the assert_called_once_with. @mock.patch.object(api_caller, 'load_legal_search_results') def test_search_pagination(self, load_legal_search_results): - load_legal_search_results.return_value = legal_test_data.regulations_search_results() - + load_legal_search_results.return_value =\ + legal_test_data.regulations_search_results() + response = client.get('/data/legal/search/regulations/', data={ 'search': 'in kind donation', 'search_type': 'regulations', 'offset': 20}) assert response.status_code == 200 - load_legal_search_results.assert_called_once_with('in kind donation', - 'regulations', offset='20') + load_legal_search_results.assert_called_once_with( + 'in kind donation', 'regulations', offset='20') # Test 6 : OK @mock.patch.object(api_caller, 'load_legal_search_results') def test_search_statutes(self, load_legal_search_results): - load_legal_search_results.return_value = legal_test_data.statutes_search_results() + load_legal_search_results.return_value =\ + legal_test_data.statutes_search_results() response = client.get('/data/legal/search/statutes/', data={ 'search': 'in kind donation', @@ -96,7 +99,8 @@ def test_search_statutes(self, load_legal_search_results): @mock.patch.object(api_caller, '_call_api') def test_result_counts(self, _call_api_mock): _call_api_mock.return_value = { - 'advisory_opinions': [{'no': 1, 'date': '2016'}, {'no': 2, 'date': '1999'}], + 'advisory_opinions': [ + {'no': 1, 'date': '2016'}, {'no': 2, 'date': '1999'}], 'statutes': [{}] * 4, 'regulations': [{}] * 5} results = api_caller.load_legal_search_results(query='president') @@ -117,9 +121,9 @@ def test_ao_landing_page(self, load_legal_search_results): # so this mocks the two different calls and then we assert they happend # http://stackoverflow.com/questions/7242433/asserting-successive-calls-to-a-mock-method calls = [ - mock.call(query='', query_type='advisory_opinions', - ao_min_issue_date=ao_min_date, ao_category=['F', 'W']), - mock.call(query='', query_type='advisory_opinions', + mock.call(query='', query_type='advisory_opinions', + ao_min_issue_date=ao_min_date, ao_category=['F', 'W']), + mock.call(query='', query_type='advisory_opinions', ao_status='Pending', ao_category='R') ] load_legal_search_results.assert_has_calls(calls, any_order=True) From c2a3659521e0f015c9fc705dca80dbfcc248fd95 Mon Sep 17 00:00:00 2001 From: Vraj Mohan <radhakrishnan.vrajmohan@gsa.gov> Date: Thu, 30 Aug 2018 17:33:08 -0700 Subject: [PATCH 47/49] Removing failing test that needs to be rewritten anyway --- fec/data/tests/test_issues.py | 83 ----------------------------------- 1 file changed, 83 deletions(-) delete mode 100644 fec/data/tests/test_issues.py diff --git a/fec/data/tests/test_issues.py b/fec/data/tests/test_issues.py deleted file mode 100644 index 55a2c141d7..0000000000 --- a/fec/data/tests/test_issues.py +++ /dev/null @@ -1,83 +0,0 @@ - -import pytest -import github3 -from django.test import Client - -from django.test import TestCase as CMSApp -from unittest import mock - - -@pytest.yield_fixture -def client(): - with client.test_request_context(): - yield CMSApp(client) - -class TestGithub: - - @pytest.fixture - def mock_repo(self): - return mock.Mock() - - @pytest.fixture - def mock_client(self, mock_repo): - client = mock.Mock() - client.repository.return_value = mock_repo - return client - - @pytest.fixture - def mock_login(self, monkeypatch, mock_client): - login = mock.Mock() - login.return_value = mock_client - monkeypatch.setattr(github3, 'login', login) - return login - - def test_missing_referer(self, client): - res = client.post_json( - ('/issue'), - {'feedback': 'i like it'}, - expect_errors=True, - ) - assert res.status_code == 422 - - def test_invalid_referer(self, client): - res = client.post_json( - ('/issue'), - {'feedback': 'i do not like it'}, - headers={'referer': 'http://fec.gov'}, - expect_errors=True, - ) - assert res.status_code == 422 - - def test_missing_input(self, client): - res = client.post_json( - ('/issue'), - {}, - headers={'referer': 'http://localhost:5000'}, - expect_errors=True, - ) - assert res.status_code == 422 - - def test_submit(self, client, mock_login, mock_client, mock_repo): - referer = 'http://localhost:5000' - mock_issue = mock.Mock() - mock_issue.to_json.return_value = {'body': 'it broke'} - mock_repo.create_issue.return_value = mock_issue - res = client.post_json( - ('/issue'), - { - 'action': 'i tried to use it', - 'feedback': 'but nothing happened', - 'about': 'i like data', - }, - headers={'referer': referer}, - ) - assert res.status_code == 201 - mock_login.assert_called_with(token=config.github_token) - mock_client.repository.assert_called_with('fecgov', 'fec') - assert len(mock_repo.create_issue.call_args_list) == 1 - args, kwargs = mock_repo.create_issue.call_args - assert referer in args[0] - assert 'i tried to use it' in kwargs['body'] - assert 'but nothing happened' in kwargs['body'] - assert 'i like data' in kwargs['body'] - assert res.json == {'body': 'it broke'} \ No newline at end of file From 30d7844e5977f3924d354549e1fd4849c5f5c879 Mon Sep 17 00:00:00 2001 From: Vraj Mohan <radhakrishnan.vrajmohan@gsa.gov> Date: Wed, 29 Aug 2018 18:02:50 -0700 Subject: [PATCH 48/49] Use pytest instead of `manage.py test` Fixes #2041 --- .circleci/config.yml | 6 ++---- .coveragerc | 2 ++ README.md | 14 ++++---------- fec/data/tests/test_legal_search.py | 2 +- pytest.ini | 3 +++ requirements.txt | 4 +++- 6 files changed, 15 insertions(+), 16 deletions(-) create mode 100644 .coveragerc create mode 100644 pytest.ini diff --git a/.circleci/config.yml b/.circleci/config.yml index 6d7c4cae0a..7f413e2ad8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -73,10 +73,8 @@ jobs: . .env/bin/activate npm run build-js npm run build-sass - cd fec - DJANGO_SETTINGS_MODULE=fec.settings.production python manage.py collectstatic --noinput -v 0 - coverage run --source='.' manage.py test - coverage xml --omit="*migrations*" + pytest + cd fec; DJANGO_SETTINGS_MODULE=fec.settings.production python manage.py collectstatic --noinput -v 0 npm run test-single - run: diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000000..f223b6341a --- /dev/null +++ b/.coveragerc @@ -0,0 +1,2 @@ +[run] +omit=*migrations* diff --git a/README.md b/README.md index cea064cbc8..519cb691a2 100644 --- a/README.md +++ b/README.md @@ -179,26 +179,20 @@ npm run test-single *Note: You may be prompted to allow `node` to accept connections; this is okay and required for the tests to run.* -To run the Python tests, run these commands in the root project directory: +To run the Python tests, run this command in the root project directory: ```bash -cd fec/ -./manage.py test +pytest ``` It's necessary to specify the Postgresql URL, which can be done on the command line, e.g.: ```bash -env DATABASE_URL=postgresql://:@/cfdm_cms_test ./manage.py test +env DATABASE_URL=postgresql://:@/cfdm_cms_test pytest ``` -For test coverage, run: -```bash -cd fec/ -coverage run --source='.' manage.py test -coverage report --omit="*migrations*" -``` +`pytest` is configured to report test coverage automatically. ## Enabling/toggling features [settings/base.py](https://github.com/fecgov/fec-cms/blob/develop/fec/fec/settings/base.py) diff --git a/fec/data/tests/test_legal_search.py b/fec/data/tests/test_legal_search.py index d54862fd81..d1e1b9d95f 100644 --- a/fec/data/tests/test_legal_search.py +++ b/fec/data/tests/test_legal_search.py @@ -9,7 +9,7 @@ client = Client() -class TestLegalSearch(unittest.TestCase): +class TestLegalSearch(TestCase): # Test1 : OK @mock.patch.object(api_caller, 'load_legal_search_results') diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000000..cfe3099eb5 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +addopts = --cov=fec fec +DJANGO_SETTINGS_MODULE = fec.settings.dev diff --git a/requirements.txt b/requirements.txt index f807066189..4936356a4d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -39,5 +39,7 @@ cg-django-uaa==1.2.0 # Testing coverage==4.5.1 -pytest==3.2.3 +pytest==3.7.4 Faker==0.8.6 +pytest-django==3.4.2 +pytest-cov==2.5.1 From c0ce12ddb4bdcf1fba45fa7bdc7a74627b37a7fd Mon Sep 17 00:00:00 2001 From: Vraj Mohan <radhakrishnan.vrajmohan@gsa.gov> Date: Fri, 31 Aug 2018 09:31:50 -0700 Subject: [PATCH 49/49] Run `collectstatic` as part of post-test work Also, run all the npm tests before the Python tests. --- .circleci/config.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7f413e2ad8..c67019fd61 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -73,15 +73,15 @@ jobs: . .env/bin/activate npm run build-js npm run build-sass - pytest - cd fec; DJANGO_SETTINGS_MODULE=fec.settings.production python manage.py collectstatic --noinput -v 0 npm run test-single + pytest - run: - name: Perform post-test checks + name: Perform post-test work command: | . .env/bin/activate codecov + cd fec; DJANGO_SETTINGS_MODULE=fec.settings.production python manage.py collectstatic --noinput -v 0 - store_artifacts: path: test-reports