Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions fec/data/templates/elections.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

{% block css %}
<link rel="stylesheet" type="text/css" href="{{ path_for_css('elections.css') }}" />
<link rel="stylesheet" type="text/css" href="/static/css/widgets/party-money-bars.css" />
{% endblock %}
{% block body %}
{{ header.header(title, crumb) }}
Expand Down Expand Up @@ -56,13 +57,15 @@
election_full: true,
duration: '{{ election_duration }}',
office: '{{ office }}',
office_code: '{{ office_code }}',
state: '{{ state or '' }}',
stateFull: '{{ state_full or '' }}',
district: '{{ district or '' }}'
}
};

</script>
{{ tags_for_js_chunks('election-summary.js', '')|safe }}
{#- data-init and global chunks are loaded by main.jinja -#}
{{ tags_for_js_chunks('elections.js', '')|safe }}
{% endblock %}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
<div class="slab slab--inline slab--neutral u-padding--left u-padding--right">
{{ select.election_cycle_select(cycles, cycle, 'summary') }}

{% include 'partials/elections/raising-and-spending.jinja' %}
<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</h3>
Expand Down
50 changes: 50 additions & 0 deletions fec/data/templates/partials/elections/raising-and-spending.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{# used inside fec/data/templates/house-senate-overview.jinja #}
{% import 'macros/null.jinja' as null %}
{% import 'macros/disclaimer.jinja' as disclaimer %}
{% import 'macros/cycle-select.jinja' as select %}
<div id="raising-spending-summary" class="entity__figure row">
<h3 class="heading__left">Raising and spending summary</h3>
<section id="summary" class="option">
<div id="election-summary" class="heading--section heading--with-action u-no-margin">
<div id="election-summary">
<div class="grid--flex grid--3-wide">
<div class="grid__item">
<figure class="js-party-money-bars" id="summary-total-raised" data-event-field-id="total_receipts">
<div class="total-wrapper">
<h1 class="value js-value-large">$1,234,567,890</h1>
<h2 class="description js-value-large-desc">raised by candidates</h2>
</div>
<div class="visual-divider" role="presentation"></div>
<div class="parties-wrapper"></div>
</figure>
</div>
<div class="grid__item">
<figure class="js-party-money-bars" id="summary-total-spent" data-event-field-id="total_disbursements">
<div class="total-wrapper">
<h1 class="value js-value-large">$1,234,567,890</h1>
<h2 class="description js-value-large-desc">spent by candidates</h2>
</div>
<div class="visual-divider" role="presentation"></div>
<div class="parties-wrapper"></div>
</figure>
</div>
<div class="grid__item">
<figure class="js-party-money-bars" id="summary-remaining-coh" data-event-field-id="total_cash_on_hand_end_period">
<div class="total-wrapper">
<h1 class="value js-value-large">$1,234,567,890</h1>
<h2 class="description js-value-large-desc">cash on hand for candidates</h2>
</div>
<div class="visual-divider" role="presentation"></div>
<div class="parties-wrapper"></div>
</figure>
</div>
</div>
<div class="content__section--ruled-responsive methodology-block t-serif">
<div class="heading--with-action t-small t-serif">
<i class="data-disclaimer">Newly filed summary data may not appear for up to 48 hours. Money raised includes both itemized and unitemized totals. Independent expenditure totals are combined for all candidates, exclude 24- and 48-hour reports, and may take 30 days or more to appear.</i>
<div class="heading__right">
</div>
</div>
</div>
</section>
</div>
1 change: 1 addition & 0 deletions fec/data/templates/partials/elections/sidebar-nav.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
Election data and reporting deadlines
</a>
<ul>
<li><a href="#raising-spending-summary">Raising and spending summary</a></li>
<li><a href="#candidate-financial-totals">Candidate financial totals</a></li>
<li><a href="#individual-contributions">Individual contributions to candidates</a></li>
<li><a href="#independent-expenditures">Independent expenditures</a></li>
Expand Down
189 changes: 189 additions & 0 deletions fec/fec/static/js/pages/election-summary.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
/**
*
*/
import FECContainerQuery from '../modules/container-queries.js';
import PartyMoneyBars from '../modules/party-money-bars.js';

/**
* Runs the Summary tab at /data/elections/house/ and /data/elections/senate/
* @class
* @property {HTMLInputElement} cycleSelector - the <input>
*/
export default function ElectionSummary() {
this.tabPanel = document.querySelector('#election-summary');
this.cycleSelector = document.querySelector('.cycle-select #summary-cycle');

this.init();
this.startLoadingData(this.cycleSelector.value);
}

/**
* Gets this instance built and working
*/
ElectionSummary.prototype.init = function() {
this.cycleSelector.addEventListener('change', this.handleCycleChange.bind(this));

const theFigures = document.querySelectorAll('#election-summary figure');
theFigures.forEach(el => {
// const elId = el.id;
new PartyMoneyBars(
`#${el.id} .parties-wrapper`,
`#${el.id} .js-value-large`,
{
eventId: el.dataset.eventFieldId,
figureGroupClasses: 'grid--flex grid--3-wide',
figureClasses: 'grid__item'
}
);
});

new FECContainerQuery('#election-summary', '#summary');
};

/**
* Takes an election year, deactivates cycleSelect, and starts the data load
* @param {number|string} electionYear - Which election year / cycle to load
*/
ElectionSummary.prototype.startLoadingData = function(electionYear) {
// Only do anything if electionYear is an year between 1970 and 2050
//if (parseInt(electionYear) && electionYear >= 1970 && electionYear <= 2050 && electionYear % 2 === 0) {
//this.deactivateInput();

const instance = this;

const url_params = {
aggregate_by: 'office-state-district',
api_key: window.API_KEY_PUBLIC,
election_full: true,
election_year: '',
is_active_candidate: true,
office: '',
district: '',
state: '',
page: 1,
per_page: 10,
sort_hide_null: false,
sort_null_only: false,
sort_nulls_last: false
};

let theURL = `${window.API_LOCATION}/${window.API_VERSION}/candidates/totals/aggregates/?`;

url_params.election_year = electionYear;
url_params.office = window.context.election.office_code.toUpperCase();
url_params.state = window.context.election.state;
url_params.district = window.context.election.district;
for (let n in url_params) {
theURL += `${n}=${url_params[n]}&`;
}
// Object to hold totals plus by-party totals
const allTotals = {};
fetch(
theURL,
{ cache: 'no-cache', mode: 'cors' }
)
.then(response => response.json())
.then(data => {
//instance.handleDataLoaded(data.results);
// Get the totals
allTotals['total'] = data.results;
})
.catch(() => {
// console.log(`The fetch failed because: ${error}`);
});

const partys = ['DEM', 'REP', 'OTHER'];

for (const party of partys) {
fetch(
theURL +`party=${party}`,
{ cache: 'no-cache', mode: 'cors' }
)
.then(response => response.json())
.then(data => {
// Add the the by-party totals
allTotals[party] = data.results;
// TODO: Some way to just run this once instead of 3 times (for each iteration)
instance.handleDataLoaded(allTotals);
})
.catch(() => {
// console.log(`The fetch failed because: ${error}`);
});
}

//} //end if

};

/**
* Removes functionality from the input/select assigned to this.cycleSelector
*/
ElectionSummary.prototype.deactivateInput = function() {
this.cycleSelector.setAttribute('aria-disabled', 'true');
this.cycleSelector.setAttribute('disabled', 'true');
};

/**
* Restores functionality from the input/select assigned to this.cycleSelector
*/
ElectionSummary.prototype.reactivateInput = function() {
this.cycleSelector.removeAttribute('aria-disabled');
this.cycleSelector.removeAttribute('disabled');
};

/**
* Handles the change/input event from this.cycleSelector
* @param {Event} e - Change event
* @param {number} e.target.value - A valid 4-digit integer for an even/election year
*/
ElectionSummary.prototype.handleCycleChange = function (e) {
this.startLoadingData(e.target.value);
};

/**
* Takes the results, parses them from parties divisions to type combinations, then reactivates cycleSelect.
* (Takes [{dem numbers}, {rep numbers}, {other numbers}] and converts to [{receipts: {total, dem numbers, rep numbers, other numbers}}])
* @param {Object} results - response.results from the api
*/

ElectionSummary.prototype.handleDataLoaded = function(results) {
const usefulResults = {
total_cash_on_hand_end_period: { total: 0, DEM: 0, REP: 0, Other: 0 },
total_debts_owed_by_committee: { total: 0, DEM: 0, REP: 0, Other: 0 },
total_disbursements: { total: 0, DEM: 0, REP: 0, Other: 0 },
total_individual_itemized_contributions: { total: 0, DEM: 0, REP: 0, Other: 0 },
total_other_political_committee_contributions: { total: 0, DEM: 0, REP: 0, Other: 0 },
total_receipts: { total: 0, DEM: 0, REP: 0, Other: 0 },
total_transfers_from_other_authorized_committee: { total: 0, DEM: 0, REP: 0, Other: 0 }
};

for (const key in results) {
for (const val of Object.values(results[key])) {
for (const item in val) {
if (item.indexOf('total_') === 0) {
usefulResults[item][key] = results[key][0][item];
}
}
}
}

for (let key in usefulResults) {
const newEvent = new CustomEvent('fec_data_refresh', {
bubbles: true,
detail: {
id: key,
value: usefulResults[key]
}
});
this.tabPanel.dispatchEvent(newEvent);
}

this.reactivateInput();
};

/**
* Now let's wait for the page to load and start making elements work
*/
window.addEventListener('load', () => {
new ElectionSummary();
});